Compare commits

...

35 Commits

Author SHA1 Message Date
Pepe Fagoaga 4d337c462b Revert "fix(custom): execute custom checks (#4202)"
This reverts commit 2a139e3dc7.
2024-06-17 13:17:38 +02:00
Pepe Fagoaga 61b9ecc214 chore(regions_update): Changes in regions for AWS services. (#4252)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-06-14 11:07:22 -04:00
Sergio Garcia f8f2c19454 fix(readme): update note syntax (#4250) 2024-06-13 16:05:10 -04:00
Rubén De la Torre Vico 922438a7a0 chore(network): Reduce network watchers azure check findings (#4242) 2024-06-13 15:57:44 -04:00
Pepe Fagoaga 920f98c9ef chore(regions_update): Changes in regions for AWS services. (#4248)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-06-13 15:12:10 -04:00
Pepe Fagoaga 9b1ad5dd2e chore(regions_update): Changes in regions for AWS services. (#4246)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-06-12 07:56:53 -04:00
dependabot[bot] d7a97b6e1d chore(deps): bump azure-identity from 1.16.0 to 1.16.1 (#4230)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 17:49:44 -04:00
dependabot[bot] 07db051d14 chore(deps): bump azure-identity from 1.16.0 to 1.16.1 (#4245)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 17:49:30 -04:00
dependabot[bot] 6fec85589d chore(deps-dev): bump pylint from 3.2.2 to 3.2.3 (#4229) 2024-06-11 12:59:21 -04:00
dependabot[bot] f82aa1c3e1 chore(deps-dev): bump pytest from 8.2.1 to 8.2.2 (#4223) 2024-06-11 12:10:27 -04:00
Pepe Fagoaga ee9faedbbe docs(developer-guide): How to fork the repo (#4238) 2024-06-11 12:08:54 -04:00
Pepe Fagoaga e5dec1251d fix(s3): Send HTML also (#4240) 2024-06-11 12:08:13 -04:00
Pepe Fagoaga 692a39b08f chore(regions_update): Changes in regions for AWS services. (#4241) 2024-06-11 12:04:51 -04:00
Pepe Fagoaga 60b3523def chore(release): 4.2.4 (#4236) 2024-06-11 09:46:33 -04:00
Rubén De la Torre Vico e1428bc1ff chore(iam): improve iam user console access check (#4211) 2024-06-11 12:45:29 +02:00
dependabot[bot] 0ff8b7e02a chore(deps): bump boto3 from 1.34.113 to 1.34.123 (#4235)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 11:56:02 +02:00
dependabot[bot] 7b84008046 chore(deps): bump google-api-python-client from 2.131.0 to 2.132.0 (#4227)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 11:02:08 +02:00
dependabot[bot] 30a092e2aa chore(deps): bump slack-sdk from 3.27.2 to 3.28.0 (#4228)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 09:54:38 +02:00
dependabot[bot] 11a7ff2977 chore(deps): bump trufflesecurity/trufflehog from 3.77.0 to 3.78.0 (#4222)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 09:51:43 +02:00
dependabot[bot] 12ba978361 chore(deps-dev): bump safety from 3.2.0 to 3.2.3 (#4232)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 09:22:41 +02:00
dependabot[bot] 42182a2b70 chore(deps): bump botocore from 1.34.118 to 1.34.123 (#4224)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 08:37:14 +02:00
dependabot[bot] 26eaec3101 chore(deps-dev): bump authlib from 1.3.0 to 1.3.1 (#4213)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 16:47:40 -04:00
Pepe Fagoaga daf6194dee chore(regions_update): Changes in regions for AWS services. (#4210)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-06-08 16:14:04 +02:00
William Leung e28300a1db fix(config/html): handle encoding issues and improve error handling in config and HTML file loading functions (#4203)
Co-authored-by: Sergio <sergio@prowler.com>
2024-06-07 12:51:01 -04:00
Rubén De la Torre Vico 1a225c334f chore(acm): Improve near-expiration certificates check (#4207)
Co-authored-by: Sergio <sergio@prowler.com>
2024-06-07 12:22:05 -04:00
Sergio Garcia 1d64ca4372 fix(compliance): check if custom check has compliance metadata (#4208) 2024-06-07 10:54:34 -04:00
Seiji Ujihira 2a139e3dc7 fix(custom): execute custom checks (#4202) 2024-06-07 10:01:28 -04:00
Pedro Martín 89d1712ff1 fix(dashboard): fix styles in overview page (#4204) 2024-06-07 09:46:54 -04:00
Pedro Martín 45ea9e1e79 fix(html): fix status from HTML outputs (#4206) 2024-06-07 09:36:21 -04:00
Pepe Fagoaga 4b46fe9788 chore(regions_update): Changes in regions for AWS services. (#4205)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-06-07 09:31:55 -04:00
Sergio Garcia 28b9e269b7 chore(version): update Prowler version (#4201) 2024-06-07 08:40:03 +02:00
Pedro Martín 0a41ec4746 fix(html): resolve html changing finding status (#4199) 2024-06-06 11:30:49 -04:00
Pedro Martín e6472f9bfc fix(html): handle muted status to html outputs (#4195)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-06-06 10:06:02 -04:00
Pedro Martín c033af6194 docs(readme): Update checks number (#4197) 2024-06-06 09:39:24 -04:00
sansns-aws 4d662dc446 feat(rds): Add security group event subscription check (#4130)
Co-authored-by: Sergio <sergio@prowler.com>
2024-06-06 08:45:50 -04:00
41 changed files with 1811 additions and 420 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@v3.77.0
uses: trufflesecurity/trufflehog@v3.78.0
with:
path: ./
base: ${{ github.event.repository.default_branch }}
+3 -4
View File
@@ -60,7 +60,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/misc/#categories) |
|---|---|---|---|---|
| AWS | 359 | 66 -> `prowler aws --list-services` | 28 -> `prowler aws --list-compliance` | 7 -> `prowler aws --list-categories` |
| AWS | 360 | 66 -> `prowler aws --list-services` | 28 -> `prowler aws --list-compliance` | 7 -> `prowler aws --list-categories` |
| GCP | 77 | 13 -> `prowler gcp --list-services` | 1 -> `prowler gcp --list-compliance` | 2 -> `prowler gcp --list-categories`|
| Azure | 127 | 16 -> `prowler azure --list-services` | 2 -> `prowler azure --list-compliance` | 2 -> `prowler azure --list-categories` |
| Kubernetes | 83 | 7 -> `prowler kubernetes --list-services` | 1 -> `prowler kubernetes --list-compliance` | 7 -> `prowler kubernetes --list-categories` |
@@ -74,7 +74,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
pip install prowler
prowler -v
```
More details at [https://docs.prowler.com](https://docs.prowler.com/projects/prowler-open-source/en/latest/)
>More details at [https://docs.prowler.com](https://docs.prowler.com/projects/prowler-open-source/en/latest/)
## Containers
@@ -102,8 +102,7 @@ poetry shell
poetry install
python prowler.py -v
```
???+ note
If you want to clone Prowler from Windows, use `git config core.longpaths true` to allow long file paths.
> If you want to clone Prowler from Windows, use `git config core.longpaths true` to allow long file paths.
# 📐✏️ High level architecture
You can run Prowler from your workstation, a Kubernetes Job, a Google Compute Engine, an Azure VM, an EC2 instance, Fargate or any other container, CloudShell and many more.
+1 -1
View File
@@ -21,7 +21,7 @@ muted_manual_color = "#b33696"
critical_color = "#951649"
high_color = "#e11d48"
medium_color = "#ee6f15"
low_color = "#f9f5e6"
low_color = "#fcf45d"
informational_color = "#3274d9"
# Folder output path
+21 -27
View File
@@ -945,7 +945,7 @@ def filter_data(
color_mapping_status = {
"FAIL": fail_color,
"PASS": pass_color,
"INFO": info_color,
"LOW": info_color,
"MANUAL": manual_color,
"WARNING": muted_fail_color,
"MUTED (FAIL)": muted_fail_color,
@@ -1564,7 +1564,10 @@ def generate_table(data, index, color_mapping_severity, color_mapping_status):
data.get(
"FINDING_UID", ""
)
)
),
style={
"margin-left": "5px"
},
),
],
style={"display": "flex"},
@@ -1644,28 +1647,10 @@ def generate_table(data, index, color_mapping_severity, color_mapping_status):
"STATUS_EXTENDED",
"",
)
)
),
],
style={"display": "flex"},
),
html.Div(
[
html.P(
html.Strong(
"Risk: ",
style={
"margin-right": "5px"
},
)
),
html.P(
str(
data.get(
"RISK",
"",
)
)
),
style={
"margin-left": "5px"
},
),
],
style={"display": "flex"},
@@ -1689,7 +1674,10 @@ def generate_table(data, index, color_mapping_severity, color_mapping_status):
)
),
html.P(
str(data.get("RISK", ""))
str(data.get("RISK", "")),
style={
"margin-left": "5px"
},
),
],
style={"display": "flex"},
@@ -1744,7 +1732,10 @@ def generate_table(data, index, color_mapping_severity, color_mapping_status):
"REMEDIATION_RECOMMENDATION_TEXT",
"",
)
)
),
style={
"margin-left": "5px"
},
),
],
style={"display": "flex"},
@@ -1772,7 +1763,10 @@ def generate_table(data, index, color_mapping_severity, color_mapping_status):
"",
)
),
style={"color": "#3182ce"},
style={
"color": "#3182ce",
"margin-left": "5px",
},
),
],
style={"display": "flex"},
+6 -2
View File
@@ -4,10 +4,14 @@ You can extend Prowler Open Source in many different ways, in most cases you wil
## Get the code and install all dependencies
First of all, you need a version of Python 3.9 or higher and also pip installed to be able to install all dependencies required. Once that is satisfied go a head and clone the repo:
First of all, you need a version of Python 3.9 or higher and also `pip` installed to be able to install all dependencies required.
Then, to start working with the Prowler Github repository you need to fork it to be able to propose changes for new features, bug fixing, etc. To fork the Prowler repo please refer to [this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo?tool=webui#forking-a-repository).
Once that is satisfied go ahead and clone your forked repo:
```
git clone https://github.com/prowler-cloud/prowler
git clone https://github.com/<your-github-user>/prowler
cd prowler
```
For isolation and avoid conflicts with other environments, we recommend usage of `poetry`:
+233 -172
View File
@@ -29,19 +29,22 @@ The following list includes all the AWS checks with configurable variables that
| `organizations_delegated_administrators` | `organizations_trusted_delegated_administrators` | List of Strings |
| `ecr_repositories_scan_vulnerabilities_in_latest_image` | `ecr_repository_vulnerability_minimum_severity` | String |
| `trustedadvisor_premium_support_plan_subscribed` | `verify_premium_support_plans` | Boolean |
| `config_recorder_all_regions_enabled` | `mute_non_default_regions` | Boolean |
| `drs_job_exist` | `mute_non_default_regions` | Boolean |
| `guardduty_is_enabled` | `mute_non_default_regions` | Boolean |
| `securityhub_enabled` | `mute_non_default_regions` | Boolean |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_entropy` | Integer |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_minutes` | Integer |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_actions` | List of Strings |
| `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_entropy` | Integer |
| `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_minutes` | Integer |
| `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_actions` | List of Strings |
| `rds_instance_backup_enabled` | `check_rds_instance_replicas` | Boolean |
| `ec2_securitygroup_allow_ingress_from_internet_to_any_port` | `ec2_allowed_interface_types` | List of Strings |
| `ec2_securitygroup_allow_ingress_from_internet_to_any_port` | `ec2_allowed_instance_owners` | List of Strings |
| `config_recorder_all_regions_enabled` | `mute_non_default_regions` | Boolean |
| `drs_job_exist` | `mute_non_default_regions` | Boolean |
| `guardduty_is_enabled` | `mute_non_default_regions` | Boolean |
| `securityhub_enabled` | `mute_non_default_regions` | Boolean |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_entropy` | Integer |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_minutes` | Integer |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_actions` | List of Strings |
| `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_entropy` | Integer |
| `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_minutes` | Integer |
| `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_actions` | List of Strings |
| `rds_instance_backup_enabled` | `check_rds_instance_replicas` | Boolean |
| `ec2_securitygroup_allow_ingress_from_internet_to_any_port` | `ec2_allowed_interface_types` | List of Strings |
| `ec2_securitygroup_allow_ingress_from_internet_to_any_port` | `ec2_allowed_instance_owners` | List of Strings |
| `acm_certificates_expiration_check` | `days_to_expire_threshold` | Integer |
## Azure
### Configurable Checks
@@ -80,10 +83,20 @@ The following list includes all the Azure checks with configurable variables tha
```yaml title="config.yaml"
# AWS Configuration
aws:
# AWS Global Configuration
# aws.mute_non_default_regions --> Mute Failed Findings in non-default regions for GuardDuty, SecurityHub, DRS and Config
# aws.mute_non_default_regions --> Set to True to muted failed findings in non-default regions for AccessAnalyzer, GuardDuty, SecurityHub, DRS and Config
mute_non_default_regions: False
# If you want to mute failed findings only in specific regions, create a file with the following syntax and run it with `prowler aws -w mutelist.yaml`:
# Mutelist:
# Accounts:
# "*":
# Checks:
# "*":
# Regions:
# - "ap-southeast-1"
# - "ap-southeast-2"
# Resources:
# - "*"
# AWS IAM Configuration
# aws.iam_user_accesskey_unused --> CIS recommends 45 days
@@ -93,6 +106,7 @@ aws:
# AWS EC2 Configuration
# aws.ec2_elastic_ip_shodan
# TODO: create common config
shodan_api_key: null
# aws.ec2_securitygroup_with_many_ingress_egress_rules --> by default is 50 rules
max_security_group_rules: 50
@@ -102,13 +116,13 @@ aws:
# allowed network interface types for security groups open to the Internet
ec2_allowed_interface_types:
[
"api_gateway_managed",
"vpc_endpoint",
"api_gateway_managed",
"vpc_endpoint",
]
# allowed network interface owners for security groups open to the Internet
ec2_allowed_instance_owners:
[
"amazon-elb"
"amazon-elb"
]
# AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries)
@@ -133,205 +147,222 @@ aws:
# aws.awslambda_function_using_supported_runtimes
obsolete_lambda_runtimes:
[
"java8",
"go1.x",
"provided",
"python3.6",
"python2.7",
"python3.7",
"nodejs4.3",
"nodejs4.3-edge",
"nodejs6.10",
"nodejs",
"nodejs8.10",
"nodejs10.x",
"nodejs12.x",
"nodejs14.x",
"dotnet5.0",
"dotnetcore1.0",
"dotnetcore2.0",
"dotnetcore2.1",
"dotnetcore3.1",
"ruby2.5",
"ruby2.7",
]
# AWS Organizations
# organizations_scp_check_deny_regions
# organizations_enabled_regions: [
# 'eu-central-1',
# 'eu-west-1',
# aws.organizations_scp_check_deny_regions
# aws.organizations_enabled_regions: [
# "eu-central-1",
# "eu-west-1",
# "us-east-1"
# ]
organizations_enabled_regions: []
organizations_trusted_delegated_administrators: []
# AWS ECR
# ecr_repositories_scan_vulnerabilities_in_latest_image
# aws.ecr_repositories_scan_vulnerabilities_in_latest_image
# CRITICAL
# HIGH
# MEDIUM
ecr_repository_vulnerability_minimum_severity: "MEDIUM"
# AWS Trusted Advisor
# trustedadvisor_premium_support_plan_subscribed
# aws.trustedadvisor_premium_support_plan_subscribed
verify_premium_support_plans: True
# AWS CloudTrail Configuration
# aws.cloudtrail_threat_detection_privilege_escalation
threat_detection_privilege_escalation_entropy: 0.7 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.7 (70%)
threat_detection_privilege_escalation_threshold: 0.1 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.1 (10%)
threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours)
threat_detection_privilege_escalation_actions: [
"AddPermission",
"AddRoleToInstanceProfile",
"AddUserToGroup",
"AssociateAccessPolicy",
"AssumeRole",
"AttachGroupPolicy",
"AttachRolePolicy",
"AttachUserPolicy",
"ChangePassword",
"CreateAccessEntry",
"CreateAccessKey",
"CreateDevEndpoint",
"CreateEventSourceMapping",
"CreateFunction",
"CreateGroup",
"CreateJob",
"CreateKeyPair",
"CreateLoginProfile",
"CreatePipeline",
"CreatePolicyVersion",
"CreateRole",
"CreateStack",
"DeleteRolePermissionsBoundary",
"DeleteRolePolicy",
"DeleteUserPermissionsBoundary",
"DeleteUserPolicy",
"DetachRolePolicy",
"DetachUserPolicy",
"GetCredentialsForIdentity",
"GetId",
"GetPolicyVersion",
"GetUserPolicy",
"Invoke",
"ModifyInstanceAttribute",
"PassRole",
"PutGroupPolicy",
"PutPipelineDefinition",
"PutRolePermissionsBoundary",
"PutRolePolicy",
"PutUserPermissionsBoundary",
"PutUserPolicy",
"ReplaceIamInstanceProfileAssociation",
"RunInstances",
"SetDefaultPolicyVersion",
"UpdateAccessKey",
"UpdateAssumeRolePolicy",
"UpdateDevEndpoint",
"UpdateEventSourceMapping",
"UpdateFunctionCode",
"UpdateJob",
"UpdateLoginProfile",
]
threat_detection_privilege_escalation_actions:
[
"AddPermission",
"AddRoleToInstanceProfile",
"AddUserToGroup",
"AssociateAccessPolicy",
"AssumeRole",
"AttachGroupPolicy",
"AttachRolePolicy",
"AttachUserPolicy",
"ChangePassword",
"CreateAccessEntry",
"CreateAccessKey",
"CreateDevEndpoint",
"CreateEventSourceMapping",
"CreateFunction",
"CreateGroup",
"CreateJob",
"CreateKeyPair",
"CreateLoginProfile",
"CreatePipeline",
"CreatePolicyVersion",
"CreateRole",
"CreateStack",
"DeleteRolePermissionsBoundary",
"DeleteRolePolicy",
"DeleteUserPermissionsBoundary",
"DeleteUserPolicy",
"DetachRolePolicy",
"DetachUserPolicy",
"GetCredentialsForIdentity",
"GetId",
"GetPolicyVersion",
"GetUserPolicy",
"Invoke",
"ModifyInstanceAttribute",
"PassRole",
"PutGroupPolicy",
"PutPipelineDefinition",
"PutRolePermissionsBoundary",
"PutRolePolicy",
"PutUserPermissionsBoundary",
"PutUserPolicy",
"ReplaceIamInstanceProfileAssociation",
"RunInstances",
"SetDefaultPolicyVersion",
"UpdateAccessKey",
"UpdateAssumeRolePolicy",
"UpdateDevEndpoint",
"UpdateEventSourceMapping",
"UpdateFunctionCode",
"UpdateJob",
"UpdateLoginProfile",
]
# aws.cloudtrail_threat_detection_enumeration
threat_detection_enumeration_entropy: 0.7 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.7 (70%)
threat_detection_enumeration_threshold: 0.1 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.1 (10%)
threat_detection_enumeration_minutes: 1440 # Past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours)
threat_detection_enumeration_actions: [
"DescribeAccessEntry",
"DescribeAccountAttributes",
"DescribeAvailabilityZones",
"DescribeBundleTasks",
"DescribeCarrierGateways",
"DescribeClientVpnRoutes",
"DescribeCluster",
"DescribeDhcpOptions",
"DescribeFlowLogs",
"DescribeImages",
"DescribeInstanceAttribute",
"DescribeInstanceInformation",
"DescribeInstanceTypes",
"DescribeInstances",
"DescribeInstances",
"DescribeKeyPairs",
"DescribeLogGroups",
"DescribeLogStreams",
"DescribeOrganization",
"DescribeRegions",
"DescribeSecurityGroups",
"DescribeSnapshotAttribute",
"DescribeSnapshotTierStatus",
"DescribeSubscriptionFilters",
"DescribeTransitGatewayMulticastDomains",
"DescribeVolumes",
"DescribeVolumesModifications",
"DescribeVpcEndpointConnectionNotifications",
"DescribeVpcs",
"GetAccount",
"GetAccountAuthorizationDetails",
"GetAccountSendingEnabled",
"GetBucketAcl",
"GetBucketLogging",
"GetBucketPolicy",
"GetBucketReplication",
"GetBucketVersioning",
"GetCallerIdentity",
"GetCertificate",
"GetConsoleScreenshot",
"GetCostAndUsage",
"GetDetector",
"GetEbsDefaultKmsKeyId",
"GetEbsEncryptionByDefault",
"GetFindings",
"GetFlowLogsIntegrationTemplate",
"GetIdentityVerificationAttributes",
"GetInstances",
"GetIntrospectionSchema",
"GetLaunchTemplateData",
"GetLaunchTemplateData",
"GetLogRecord",
"GetParameters",
"GetPolicyVersion",
"GetPublicAccessBlock",
"GetQueryResults",
"GetRegions",
"GetSMSAttributes",
"GetSMSSandboxAccountStatus",
"GetSendQuota",
"GetTransitGatewayRouteTableAssociations",
"GetUserPolicy",
"HeadObject",
"ListAccessKeys",
"ListAccounts",
"ListAllMyBuckets",
"ListAssociatedAccessPolicies",
"ListAttachedUserPolicies",
"ListClusters",
"ListDetectors",
"ListDomains",
"ListFindings",
"ListHostedZones",
"ListIPSets",
"ListIdentities",
"ListInstanceProfiles",
"ListObjects",
"ListOrganizationalUnitsForParent",
"ListOriginationNumbers",
"ListPolicyVersions",
"ListRoles",
"ListRoles",
"ListRules",
"ListServiceQuotas",
"ListSubscriptions",
"ListTargetsByRule",
"ListTopics",
"ListUsers",
"LookupEvents",
"Search",
]
threat_detection_enumeration_actions:
[
"DescribeAccessEntry",
"DescribeAccountAttributes",
"DescribeAvailabilityZones",
"DescribeBundleTasks",
"DescribeCarrierGateways",
"DescribeClientVpnRoutes",
"DescribeCluster",
"DescribeDhcpOptions",
"DescribeFlowLogs",
"DescribeImages",
"DescribeInstanceAttribute",
"DescribeInstanceInformation",
"DescribeInstanceTypes",
"DescribeInstances",
"DescribeInstances",
"DescribeKeyPairs",
"DescribeLogGroups",
"DescribeLogStreams",
"DescribeOrganization",
"DescribeRegions",
"DescribeSecurityGroups",
"DescribeSnapshotAttribute",
"DescribeSnapshotTierStatus",
"DescribeSubscriptionFilters",
"DescribeTransitGatewayMulticastDomains",
"DescribeVolumes",
"DescribeVolumesModifications",
"DescribeVpcEndpointConnectionNotifications",
"DescribeVpcs",
"GetAccount",
"GetAccountAuthorizationDetails",
"GetAccountSendingEnabled",
"GetBucketAcl",
"GetBucketLogging",
"GetBucketPolicy",
"GetBucketReplication",
"GetBucketVersioning",
"GetCallerIdentity",
"GetCertificate",
"GetConsoleScreenshot",
"GetCostAndUsage",
"GetDetector",
"GetEbsDefaultKmsKeyId",
"GetEbsEncryptionByDefault",
"GetFindings",
"GetFlowLogsIntegrationTemplate",
"GetIdentityVerificationAttributes",
"GetInstances",
"GetIntrospectionSchema",
"GetLaunchTemplateData",
"GetLaunchTemplateData",
"GetLogRecord",
"GetParameters",
"GetPolicyVersion",
"GetPublicAccessBlock",
"GetQueryResults",
"GetRegions",
"GetSMSAttributes",
"GetSMSSandboxAccountStatus",
"GetSendQuota",
"GetTransitGatewayRouteTableAssociations",
"GetUserPolicy",
"HeadObject",
"ListAccessKeys",
"ListAccounts",
"ListAllMyBuckets",
"ListAssociatedAccessPolicies",
"ListAttachedUserPolicies",
"ListClusters",
"ListDetectors",
"ListDomains",
"ListFindings",
"ListHostedZones",
"ListIPSets",
"ListIdentities",
"ListInstanceProfiles",
"ListObjects",
"ListOrganizationalUnitsForParent",
"ListOriginationNumbers",
"ListPolicyVersions",
"ListRoles",
"ListRoles",
"ListRules",
"ListServiceQuotas",
"ListSubscriptions",
"ListTargetsByRule",
"ListTopics",
"ListUsers",
"LookupEvents",
"Search",
]
# AWS RDS Configuration
# aws.rds_instance_backup_enabled
# Whether to check RDS instance replicas or not
check_rds_instance_replicas: False
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
# Azure Configuration
azure:
# Azure Network Configuration
# azure.network_public_ip_shodan
# TODO: create common config
shodan_api_key: null
# Azure App Configuration
# Azure App Service
# azure.app_ensure_php_version_is_latest
php_latest_version: "8.2"
# azure.app_ensure_python_version_is_latest
@@ -345,4 +376,34 @@ gcp:
# gcp.compute_public_address_shodan
shodan_api_key: null
# Kubernetes Configuration
kubernetes:
# Kubernetes API Server
# kubernetes.apiserver_audit_log_maxbackup_set
audit_log_maxbackup: 10
# kubernetes.apiserver_audit_log_maxsize_set
audit_log_maxsize: 100
# kubernetes.apiserver_audit_log_maxage_set
audit_log_maxage: 30
# kubernetes.apiserver_strong_ciphers_only
apiserver_strong_ciphers:
[
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
]
# Kubelet
# kubernetes.kubelet_strong_ciphers_only
kubelet_strong_ciphers:
[
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
]
```
+1 -1
View File
@@ -125,7 +125,7 @@ The JSON-OCSF output format implements the [Detection Finding](https://schema.oc
"product": {
"name": "Prowler",
"vendor_name": "Prowler",
"version": "4.2.2"
"version": "4.2.4"
},
"version": "1.1.0"
},
+6
View File
@@ -11,6 +11,12 @@ prowler <provider> --scan-unused-services
## Services that are ignored
### AWS
#### ACM
You can have certificates in ACM that is not in use by any AWS resource.
Prowler will check if every certificate is going to expire soon, if this certificate is not in use by default it is not going to be check if it is expired, is going to expire soon or it is good.
- `acm_certificates_expiration_check`
#### Athena
When you create an AWS Account, Athena will create a default primary workgroup for you.
Prowler will check if that workgroup is enabled and if it is being used by checking if there were queries in the last 45 days.
Generated
+30 -30
View File
@@ -215,13 +215,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p
[[package]]
name = "authlib"
version = "1.3.0"
version = "1.3.1"
description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients."
optional = false
python-versions = ">=3.8"
files = [
{file = "Authlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:9637e4de1fb498310a56900b3e2043a206b03cb11c05422014b0302cbc814be3"},
{file = "Authlib-1.3.0.tar.gz", hash = "sha256:959ea62a5b7b5123c5059758296122b57cd2585ae2ed1c0622c21b371ffdae06"},
{file = "Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377"},
{file = "authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917"},
]
[package.dependencies]
@@ -305,13 +305,13 @@ aio = ["aiohttp (>=3.0)"]
[[package]]
name = "azure-identity"
version = "1.16.0"
version = "1.16.1"
description = "Microsoft Azure Identity Library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "azure-identity-1.16.0.tar.gz", hash = "sha256:6ff1d667cdcd81da1ceab42f80a0be63ca846629f518a922f7317a7e3c844e1b"},
{file = "azure_identity-1.16.0-py3-none-any.whl", hash = "sha256:722fdb60b8fdd55fa44dc378b8072f4b419b56a5e54c0de391f644949f3a826f"},
{file = "azure-identity-1.16.1.tar.gz", hash = "sha256:6d93f04468f240d59246d8afde3091494a5040d4f141cad0f49fc0c399d0d91e"},
{file = "azure_identity-1.16.1-py3-none-any.whl", hash = "sha256:8fb07c25642cd4ac422559a8b50d3e77f73dcc2bbfaba419d06d6c9d7cff6726"},
]
[package.dependencies]
@@ -708,17 +708,17 @@ files = [
[[package]]
name = "boto3"
version = "1.34.113"
version = "1.34.123"
description = "The AWS SDK for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "boto3-1.34.113-py3-none-any.whl", hash = "sha256:7e59f0a848be477a4c98a90e7a18a0e284adfb643f7879d2b303c5f493661b7a"},
{file = "boto3-1.34.113.tar.gz", hash = "sha256:009cd143509f2ff4c37582c3f45d50f28c95eed68e8a5c36641206bdb597a9ea"},
{file = "boto3-1.34.123-py3-none-any.whl", hash = "sha256:56bec52d485d5670ce96d53ae7b2cd4ae4e8a705fb2298a21093cdd77d642331"},
{file = "boto3-1.34.123.tar.gz", hash = "sha256:42b140fc850cf261ee4b1e8ef527fa071b1f1592a6d6a68d34b29f37cc46b4dd"},
]
[package.dependencies]
botocore = ">=1.34.113,<1.35.0"
botocore = ">=1.34.123,<1.35.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0"
@@ -727,13 +727,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.34.118"
version = "1.34.123"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">=3.8"
files = [
{file = "botocore-1.34.118-py3-none-any.whl", hash = "sha256:e3f6c5636a4394768e81e33a16f5c6ae7f364f512415d423f9b9dc67fc638df4"},
{file = "botocore-1.34.118.tar.gz", hash = "sha256:0a3d1ec0186f8b516deb39474de3d226d531f77f92a0f56ad79b80219db3ae9e"},
{file = "botocore-1.34.123-py3-none-any.whl", hash = "sha256:8c34ada2a708c82e7174bff700611643db7ce2cb18f1130c35045c24310d299d"},
{file = "botocore-1.34.123.tar.gz", hash = "sha256:a8577f6574600c4d159b5cd103ee05744a443d77f7778304e17307940b369c4f"},
]
[package.dependencies]
@@ -745,7 +745,7 @@ urllib3 = [
]
[package.extras]
crt = ["awscrt (==0.20.9)"]
crt = ["awscrt (==0.20.11)"]
[[package]]
name = "cachetools"
@@ -1587,13 +1587,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]]
name = "google-api-python-client"
version = "2.131.0"
version = "2.132.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "google-api-python-client-2.131.0.tar.gz", hash = "sha256:1c03e24af62238a8817ecc24e9d4c32ddd4cb1f323b08413652d9a9a592fc00d"},
{file = "google_api_python_client-2.131.0-py2.py3-none-any.whl", hash = "sha256:e325409bdcef4604d505d9246ce7199960a010a0569ac503b9f319db8dbdc217"},
{file = "google-api-python-client-2.132.0.tar.gz", hash = "sha256:d6340dc83b72d72333cee5d50f7dcfecbff66a8783164090e945f985ec4c374d"},
{file = "google_api_python_client-2.132.0-py2.py3-none-any.whl", hash = "sha256:cde87700bd4d37f39f5e940292c1c6cd0910990b5b01f50b1332a8cea38e8595"},
]
[package.dependencies]
@@ -3492,13 +3492,13 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]]
name = "pylint"
version = "3.2.2"
version = "3.2.3"
description = "python code static checker"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "pylint-3.2.2-py3-none-any.whl", hash = "sha256:3f8788ab20bb8383e06dd2233e50f8e08949cfd9574804564803441a4946eab4"},
{file = "pylint-3.2.2.tar.gz", hash = "sha256:d068ca1dfd735fb92a07d33cb8f288adc0f6bc1287a139ca2425366f7cbe38f8"},
{file = "pylint-3.2.3-py3-none-any.whl", hash = "sha256:b3d7d2708a3e04b4679e02d99e72329a8b7ee8afb8d04110682278781f889fa8"},
{file = "pylint-3.2.3.tar.gz", hash = "sha256:02f6c562b215582386068d52a30f520d84fdbcf2a95fc7e855b816060d048b60"},
]
[package.dependencies]
@@ -3554,13 +3554,13 @@ diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pytest"
version = "8.2.1"
version = "8.2.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"},
{file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"},
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
]
[package.dependencies]
@@ -4220,13 +4220,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"]
[[package]]
name = "safety"
version = "3.2.0"
version = "3.2.3"
description = "Checks installed dependencies for known vulnerabilities and licenses."
optional = false
python-versions = ">=3.7"
files = [
{file = "safety-3.2.0-py3-none-any.whl", hash = "sha256:a432fc9d17e79a4386c4f093656b617c56f839cde022649cfa796d72c7a544de"},
{file = "safety-3.2.0.tar.gz", hash = "sha256:8bd5cab5f3d8a61ce0ea6e98f267c1006d056097c45c644fee7afeff7d5949c1"},
{file = "safety-3.2.3-py3-none-any.whl", hash = "sha256:cda1e91749f610337a18b7f21f78267c127e44ebbbbcbbd419c83284279a5024"},
{file = "safety-3.2.3.tar.gz", hash = "sha256:414154934f1727daf8a6473493944fecb380540c3f00875dc1ae377382f7d83f"},
]
[package.dependencies]
@@ -4353,13 +4353,13 @@ files = [
[[package]]
name = "slack-sdk"
version = "3.27.2"
version = "3.28.0"
description = "The Slack API Platform SDK for Python"
optional = false
python-versions = ">=3.6"
files = [
{file = "slack_sdk-3.27.2-py2.py3-none-any.whl", hash = "sha256:af97158e6ac7f667e158e8036e63dc1f79db9bd36216a33c10fcc49be7c2f30c"},
{file = "slack_sdk-3.27.2.tar.gz", hash = "sha256:bb145bf2bd93b60a17cd55c05cb15868c9a07d845b6fb608c798b50bce21cb99"},
{file = "slack_sdk-3.28.0-py2.py3-none-any.whl", hash = "sha256:1a47700ae20566575ce494d1d1b6f594b011d06aad28e3b8e28c052cad1d6c4c"},
{file = "slack_sdk-3.28.0.tar.gz", hash = "sha256:e6ece5cb70850492637e002e3b0d26d307939f4a33203b88cb274f7475c9a144"},
]
[package.extras]
@@ -4907,4 +4907,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.13"
content-hash = "450da57ae7375ff59256f54de76da7e8aad6e2f531cd6614bfc5f59d6489c9ef"
content-hash = "6e50f55942ded1ce410d4119d25807608dd602b3c28c53b28602b42682841a59"
+59 -24
View File
@@ -1,6 +1,5 @@
import os
import pathlib
import sys
from datetime import datetime, timezone
from os import getcwd
@@ -11,7 +10,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "4.2.2"
prowler_version = "4.2.4"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
@@ -65,6 +64,7 @@ default_config_file_path = (
default_fixer_config_file_path = (
f"{pathlib.Path(os.path.dirname(os.path.realpath(__file__)))}/fixer_config.yaml"
)
available_output_formats = ["csv", "json-asff", "json-ocsf", "html"]
def get_default_mute_file_path(provider: str):
@@ -99,52 +99,87 @@ def check_current_version():
def load_and_validate_config_file(provider: str, config_file_path: str) -> dict:
"""
load_and_validate_config_file reads the Prowler config file in YAML format from the default location or the file passed with the --config-file flag
Reads the Prowler config file in YAML format from the default location or the file passed with the --config-file flag.
Args:
provider (str): The provider name (e.g., 'aws', 'gcp', 'azure', 'kubernetes').
config_file_path (str): The path to the configuration file.
Returns:
dict: The configuration dictionary for the specified provider.
"""
try:
with open(config_file_path) as f:
config = {}
with open(config_file_path, "r", encoding="utf-8") as f:
config_file = yaml.safe_load(f)
# Not to introduce a breaking change we have to allow the old format config file without any provider keys
# and a new format with a key for each provider to include their configuration values within
# Check if the new format is passed
if (
"aws" in config_file
or "gcp" in config_file
or "azure" in config_file
or "kubernetes" in config_file
):
# Not to introduce a breaking change, allow the old format config file without any provider keys
# and a new format with a key for each provider to include their configuration values within.
if any(key in config_file for key in ["aws", "gcp", "azure", "kubernetes"]):
config = config_file.get(provider, {})
else:
config = config_file if config_file else {}
# Not to break Azure, K8s and GCP does not support neither use the old config format
# Not to break Azure, K8s and GCP does not support or use the old config format
if provider in ["azure", "gcp", "kubernetes"]:
config = {}
return config
except Exception as error:
logger.critical(
except FileNotFoundError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
sys.exit(1)
except yaml.YAMLError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
except UnicodeDecodeError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
return {}
def load_and_validate_fixer_config_file(
provider: str, fixer_config_file_path: str
) -> dict:
"""
load_and_validate_fixer_config_file reads the Prowler fixer config file in YAML format from the default location or the file passed with the --fixer-config flag
Reads the Prowler fixer config file in YAML format from the default location or the file passed with the --fixer-config flag.
Args:
provider (str): The provider name (e.g., 'aws', 'gcp', 'azure', 'kubernetes').
fixer_config_file_path (str): The path to the fixer configuration file.
Returns:
dict: The fixer configuration dictionary for the specified provider.
Raises:
SystemExit: If there is an error reading or parsing the fixer configuration file.
"""
try:
with open(fixer_config_file_path) as f:
with open(fixer_config_file_path, "r", encoding="utf-8") as f:
fixer_config_file = yaml.safe_load(f)
return fixer_config_file.get(provider, {})
except Exception as error:
logger.critical(
except FileNotFoundError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
sys.exit(1)
except yaml.YAMLError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
except UnicodeDecodeError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
return {}
+6
View File
@@ -262,10 +262,16 @@ aws:
"LookupEvents",
"Search",
]
# AWS RDS Configuration
# aws.rds_instance_backup_enabled
# Whether to check RDS instance replicas or not
check_rds_instance_replicas: False
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
# Azure Configuration
azure:
# Azure Network Configuration
+2 -1
View File
@@ -5,6 +5,7 @@ from argparse import RawTextHelpFormatter
from dashboard.lib.arguments.arguments import init_dashboard_parser
from prowler.config.config import (
available_compliance_frameworks,
available_output_formats,
check_current_version,
default_config_file_path,
default_fixer_config_file_path,
@@ -147,7 +148,7 @@ Detailed documentation at https://docs.prowler.com
nargs="+",
help="Output modes, by default csv and json-oscf are saved. When using AWS Security Hub integration, json-asff output is also saved.",
default=["csv", "json-ocsf", "html"],
choices=["csv", "json-asff", "json-ocsf", "html"],
choices=available_output_formats,
)
common_outputs_parser.add_argument(
"--output-filename",
+22 -17
View File
@@ -60,23 +60,28 @@ def get_check_compliance_frameworks_in_input(
):
"""get_check_compliance_frameworks_in_input returns a list of Compliance for the given check if the compliance framework is present in the input compliance to execute"""
check_compliances = []
if bulk_checks_metadata and bulk_checks_metadata[check_id]:
for compliance in bulk_checks_metadata[check_id].Compliance:
compliance_name = ""
if compliance.Version:
compliance_name = (
compliance.Framework.lower()
+ "_"
+ compliance.Version.lower()
+ "_"
+ compliance.Provider.lower()
)
else:
compliance_name = (
compliance.Framework.lower() + "_" + compliance.Provider.lower()
)
if compliance_name.replace("-", "_") in input_compliance_frameworks:
check_compliances.append(compliance)
try:
if bulk_checks_metadata and bulk_checks_metadata.get(check_id):
for compliance in bulk_checks_metadata[check_id].Compliance:
compliance_name = ""
if compliance.Version:
compliance_name = (
compliance.Framework.lower()
+ "_"
+ compliance.Version.lower()
+ "_"
+ compliance.Provider.lower()
)
else:
compliance_name = (
compliance.Framework.lower() + "_" + compliance.Provider.lower()
)
if compliance_name.replace("-", "_") in input_compliance_frameworks:
check_compliances.append(compliance)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return check_compliances
+25 -14
View File
@@ -135,18 +135,20 @@ def add_html_header(file_descriptor, provider):
def fill_html(file_descriptor, finding):
try:
row_class = "p-3 mb-2 bg-success-custom"
finding.status = finding.status.split(".")[0]
finding_status = finding.status.split(".")[0]
# Change the status of the finding if it's muted
if finding.muted:
finding_status = f"MUTED ({finding_status})"
row_class = "table-warning"
if finding.status == "MANUAL":
row_class = "table-info"
elif finding.status == "FAIL":
row_class = "table-danger"
elif finding.status == "WARNING":
row_class = "table-warning"
file_descriptor.write(
f"""
<tr class="{row_class}">
<td>{finding.status}</td>
<td>{finding_status}</td>
<td>{finding.severity.split(".")[0]}</td>
<td>{finding.service_name}</td>
<td>{finding.region.lower()}</td>
@@ -171,33 +173,42 @@ def fill_html(file_descriptor, finding):
def fill_html_overview_statistics(stats, output_filename, output_directory):
try:
filename = f"{output_directory}/{output_filename}{html_file_suffix}"
# Read file
# Read file
if path.isfile(filename):
with open(filename, "r") as file:
with open(filename, "r", encoding="utf-8") as file:
filedata = file.read()
# Replace statistics
# TOTAL_FINDINGS
filedata = filedata.replace(
"TOTAL_FINDINGS", str(stats.get("findings_count"))
"TOTAL_FINDINGS", str(stats.get("findings_count", 0))
)
# TOTAL_RESOURCES
filedata = filedata.replace(
"TOTAL_RESOURCES", str(stats.get("resources_count"))
"TOTAL_RESOURCES", str(stats.get("resources_count", 0))
)
# TOTAL_PASS
filedata = filedata.replace("TOTAL_PASS", str(stats.get("total_pass")))
filedata = filedata.replace("TOTAL_PASS", str(stats.get("total_pass", 0)))
# TOTAL_FAIL
filedata = filedata.replace("TOTAL_FAIL", str(stats.get("total_fail")))
filedata = filedata.replace("TOTAL_FAIL", str(stats.get("total_fail", 0)))
# Write file
with open(filename, "w") as file:
with open(filename, "w", encoding="utf-8") as file:
file.write(filedata)
except Exception as error:
logger.critical(
except FileNotFoundError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
except UnicodeDecodeError as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
)
sys.exit(1)
def add_html_footer(output_filename, output_directory):
@@ -395,12 +395,17 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -1249,6 +1254,15 @@
]
}
},
"bcm-data-exports": {
"regions": {
"aws": [
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"bedrock": {
"regions": {
"aws": [
@@ -1256,9 +1270,12 @@
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-west-2"
],
@@ -2577,6 +2594,8 @@
"connectcases": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
@@ -7037,6 +7056,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -7292,6 +7312,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -8305,6 +8326,7 @@
"eu-central-2",
"eu-south-2",
"eu-west-3",
"me-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -9273,7 +9295,10 @@
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
}
},
"serverlessrepo": {
+5 -1
View File
@@ -1,5 +1,7 @@
from prowler.config.config import (
available_output_formats,
csv_file_suffix,
html_file_suffix,
json_asff_file_suffix,
json_ocsf_file_suffix,
)
@@ -14,13 +16,15 @@ def send_to_s3_bucket(
bucket_directory = get_s3_object_path(output_directory)
filename = ""
# Get only last part of the path
if output_mode in ["csv", "json-asff", "json-ocsf"]:
if output_mode in available_output_formats:
if output_mode == "csv":
filename = f"{output_filename}{csv_file_suffix}"
elif output_mode == "json-asff":
filename = f"{output_filename}{json_asff_file_suffix}"
elif output_mode == "json-ocsf":
filename = f"{output_filename}{json_ocsf_file_suffix}"
elif output_mode == "html":
filename = f"{output_filename}{html_file_suffix}"
file_name = output_directory + "/" + filename
object_name = bucket_directory + "/" + output_mode + "/" + filename
else: # Compliance output mode
@@ -1,33 +1,36 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.acm.acm_client import acm_client
DAYS_TO_EXPIRE_THRESHOLD = 7
class acm_certificates_expiration_check(Check):
def execute(self):
findings = []
for certificate in acm_client.certificates:
report = Check_Report_AWS(self.metadata())
report.region = certificate.region
if certificate.expiration_days > DAYS_TO_EXPIRE_THRESHOLD:
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} expires in {certificate.expiration_days} days."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
else:
report.status = "FAIL"
if certificate.expiration_days < 0:
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has expired ({abs(certificate.expiration_days)} days ago)."
if certificate.in_use or acm_client.provider.scan_unused_services:
report = Check_Report_AWS(self.metadata())
report.region = certificate.region
if certificate.expiration_days > acm_client.audit_config.get(
"days_to_expire_threshold", 7
):
report.status = "PASS"
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} expires in {certificate.expiration_days} days."
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
else:
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} is about to expire in {certificate.expiration_days} days."
report.status = "FAIL"
if certificate.expiration_days < 0:
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} has expired ({abs(certificate.expiration_days)} days ago)."
report.check_metadata.Severity = "high"
else:
report.status_extended = f"ACM Certificate {certificate.id} for {certificate.name} is about to expire in {certificate.expiration_days} days."
report.check_metadata.Severity = "medium"
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
report.resource_id = certificate.id
report.resource_details = certificate.name
report.resource_arn = certificate.arn
report.resource_tags = certificate.tags
findings.append(report)
findings.append(report)
return findings
@@ -50,6 +50,7 @@ class ACM(AWSService):
id=certificate["CertificateArn"].split("/")[-1],
type=certificate["Type"],
expiration_days=certificate_expiration_time,
in_use=certificate.get("InUse", False),
transparency_logging=False,
region=regional_client.region,
)
@@ -99,5 +100,6 @@ class Certificate(BaseModel):
type: str
tags: Optional[list] = []
expiration_days: int
in_use: bool
transparency_logging: Optional[bool]
region: str
@@ -274,16 +274,26 @@ class IAM(AWSService):
if not self.audit_resources or (
is_resource_filtered(user["Arn"], self.audit_resources)
):
if "PasswordLastUsed" not in user:
users.append(User(name=user["UserName"], arn=user["Arn"]))
else:
users.append(
User(
name=user["UserName"],
arn=user["Arn"],
password_last_used=user["PasswordLastUsed"],
)
try:
user_login_profile = self.client.get_login_profile(
UserName=user["UserName"]
)
except self.client.exceptions.NoSuchEntityException:
user_login_profile = None
except Exception as error:
user_login_profile = None
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
users.append(
User(
name=user["UserName"],
arn=user["Arn"],
password_last_used=user.get("PasswordLastUsed", None),
console_access=True if user_login_profile else False,
)
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -935,6 +945,7 @@ class User(BaseModel):
arn: str
mfa_devices: list[MFADevice] = []
password_last_used: Optional[datetime]
console_access: Optional[bool]
attached_policies: list[dict] = []
inline_policies: list[str] = []
tags: Optional[list] = []
@@ -16,7 +16,7 @@ class iam_user_console_access_unused(Check):
report.resource_arn = user.arn
report.resource_tags = user.tags
report.region = iam_client.region
if user.password_last_used:
if user.console_access and user.password_last_used:
time_since_insertion = (
datetime.datetime.now()
- datetime.datetime.strptime(
@@ -31,10 +31,7 @@ class iam_user_console_access_unused(Check):
report.status_extended = f"User {user.name} has logged in to the console in the past {maximum_expiration_days} days ({time_since_insertion.days} days)."
else:
report.status = "PASS"
report.status_extended = (
f"User {user.name} does not have a console password or is unused."
)
report.status_extended = f"User {user.name} does not have console access enabled or is unused."
# Append report
findings.append(report)
return findings
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "rds_instance_event_subscription_security_groups",
"CheckTitle": "Check if RDS Security Group events are subscribed.",
"CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:rds:region:account-id:es",
"Severity": "medium",
"ResourceType": "AwsRdsEventSubscription",
"Description": "Ensure that Amazon RDS event notification subscriptions are enabled for database security groups events.",
"Risk": "Amazon RDS event subscriptions for database security groups are designed to provide incident notification of events that may affect the security, availability, and reliability of the RDS database instances associated with these security groups.",
"RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/rds-controls.html#rds-22",
"Remediation": {
"Code": {
"CLI": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-db-security-groups-events.html#",
"NativeIaC": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-db-security-groups-events.html#",
"Other": "",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-db-security-groups-events.html#"
},
"Recommendation": {
"Text": "To subscribe to RDS instance event notifications, see Subscribing to Amazon RDS event notification in the Amazon RDS User Guide.",
"Url": "https://docs.aws.amazon.com/securityhub/latest/userguide/rds-controls.html#rds-22"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,41 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.rds.rds_client import rds_client
class rds_instance_event_subscription_security_groups(Check):
def execute(self):
findings = []
if rds_client.provider.scan_unused_services or rds_client.db_instances:
for db_event in rds_client.db_event_subscriptions:
report = Check_Report_AWS(self.metadata())
report.status = "FAIL"
report.status_extended = "RDS security group event categories of configuration change and failure are not subscribed."
report.resource_id = rds_client.audited_account
report.resource_arn = rds_client.__get_trail_arn_template__(
db_event.region
)
report.region = db_event.region
if db_event.source_type == "db-security-group" and db_event.enabled:
if db_event.event_list == []:
report.resource_id = db_event.id
report.resource_arn = db_event.arn
report.status = "PASS"
report.status_extended = (
"RDS security group events are subscribed."
)
elif db_event.event_list == ["configuration change"]:
report.resource_id = db_event.id
report.resource_arn = db_event.arn
report.status = "FAIL"
report.status_extended = "RDS security group event category of failure is not subscribed."
elif db_event.event_list == ["failure"]:
report.resource_id = db_event.id
report.resource_arn = db_event.arn
report.status = "FAIL"
report.status_extended = "RDS security group event category of configuration change is not subscribed."
findings.append(report)
return findings
@@ -20,6 +20,7 @@ class RDS(AWSService):
self.db_engines = {}
self.db_cluster_parameters = {}
self.db_cluster_snapshots = []
self.db_event_subscriptions = []
self.__threading_call__(self.__describe_db_instances__)
self.__threading_call__(self.__describe_db_certificate__)
self.__threading_call__(self.__describe_db_parameters__)
@@ -30,6 +31,14 @@ class RDS(AWSService):
self.__threading_call__(self.__describe_db_cluster_snapshots__)
self.__threading_call__(self.__describe_db_cluster_snapshot_attributes__)
self.__threading_call__(self.__describe_db_engine_versions__)
self.__threading_call__(self.__describe_db_event_subscriptions__)
def __get_trail_arn_template__(self, region):
return (
f"arn:{self.audited_partition}:rds:{region}:{self.audited_account}:account"
if region
else f"arn:{self.audited_partition}:rds:{self.region}:{self.audited_account}:account"
)
def __describe_db_instances__(self, regional_client):
logger.info("RDS - Describe Instances...")
@@ -385,6 +394,61 @@ class RDS(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __describe_db_event_subscriptions__(self, regional_client):
logger.info("RDS - Describe Event Subscriptions...")
try:
describe_event_subscriptions_paginator = regional_client.get_paginator(
"describe_event_subscriptions"
)
events_exist = False
for page in describe_event_subscriptions_paginator.paginate():
for event in page["EventSubscriptionsList"]:
try:
arn = f"arn:{self.audited_partition}:rds:{regional_client.region}:{self.audited_account}:es:{event['CustSubscriptionId']}"
if not self.audit_resources or (
is_resource_filtered(
arn,
self.audit_resources,
)
):
self.db_event_subscriptions.append(
EventSubscription(
id=event["CustSubscriptionId"],
arn=arn,
sns_topic_arn=event["SnsTopicArn"],
status=event["Status"],
source_type=event["SourceType"],
source_id=event.get("SourceIdsList", []),
event_list=event.get("EventCategoriesList", []),
enabled=event["Enabled"],
region=regional_client.region,
)
)
events_exist = True
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
if not events_exist:
# No Event Subscriptions for that region
self.db_event_subscriptions.append(
EventSubscription(
id="",
arn="",
sns_topic_arn="",
status="",
source_type="",
source_id=[],
event_list=[],
enabled=False,
region=regional_client.region,
)
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class Certificate(BaseModel):
id: str
@@ -469,3 +533,15 @@ class DBEngine(BaseModel):
engine: str
engine_versions: list[str]
engine_description: str
class EventSubscription(BaseModel):
id: str
arn: str
sns_topic_arn: str
status: str
source_type: str
source_id: list
event_list: list
enabled: bool
region: str
@@ -7,7 +7,6 @@ from prowler.providers.azure.azure_provider import AzureProvider
from prowler.providers.azure.lib.service.service import AzureService
########################## SQLServer
class Network(AzureService):
def __init__(self, provider: AzureProvider):
super().__init__(NetworkManagementClient, provider)
@@ -1,7 +1,7 @@
{
"Provider": "azure",
"CheckID": "network_watcher_enabled",
"CheckTitle": "Ensure that Network Watcher is 'Enabled'",
"CheckTitle": "Ensure that Network Watcher is 'Enabled' for all locations in the Azure subscription",
"CheckType": [],
"ServiceName": "network",
"SubServiceName": "",
@@ -15,12 +15,12 @@
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/Network/enable-network-watcher.html#",
"Other": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/azure/Network/enable-network-watcher.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Opting out of Network Watcher automatic enablement is a permanent change. Once you opt-out you cannot opt-in without contacting support.",
"Url": "https://docs.azure.cn/zh-cn/cli/network/watcher?view=azure-cli-latest#az_network_watcher_list"
"Url": "https://learn.microsoft.com/en-us/security/benchmark/azure/security-controls-v2-logging-threat-detection#lt-3-enable-logging-for-azure-network-activities"
}
},
"Categories": [],
@@ -3,26 +3,26 @@ from prowler.providers.azure.services.network.network_client import network_clie
class network_watcher_enabled(Check):
def execute(self) -> Check_Report_Azure:
def execute(self) -> list[Check_Report_Azure]:
findings = []
nw_locations = []
for subscription, network_watchers in network_client.network_watchers.items():
for network_watcher in network_watchers:
nw_locations.append(network_watcher.location)
for subscription, locations in network_client.locations.items():
for location in locations:
report = Check_Report_Azure(self.metadata())
report.subscription = subscription
report.resource_name = "Network Watcher"
report.location = location
report.resource_id = f"/subscriptions/{subscription}/providers/Microsoft.Network/networkWatchers/{location}"
if location not in nw_locations:
report.status = "FAIL"
report.status_extended = f"Network Watcher is not enabled for the location {location} in subscription {subscription}."
findings.append(report)
else:
report.status = "PASS"
report.status_extended = f"Network Watcher is enabled for the location {location} in subscription {subscription}."
findings.append(report)
report = Check_Report_Azure(self.metadata())
report.subscription = subscription
report.resource_name = "Network Watcher"
report.location = "Global"
report.resource_id = f"/subscriptions/{network_client.subscriptions[subscription]}/resourceGroups/NetworkWatcherRG/providers/Microsoft.Network/networkWatchers/NetworkWatcher_*"
missing_locations = set(network_client.locations[subscription]) - set(
network_watcher.location for network_watcher in network_watchers
)
if missing_locations:
report.status = "FAIL"
report.status_extended = f"Network Watcher is not enabled for the following locations in subscription '{subscription}': {', '.join(missing_locations)}."
else:
report.status = "PASS"
report.status_extended = f"Network Watcher is enabled for all locations in subscription '{subscription}'."
findings.append(report)
return findings
+9 -9
View File
@@ -23,12 +23,12 @@ packages = [
{include = "dashboard"}
]
readme = "README.md"
version = "4.2.2"
version = "4.2.4"
[tool.poetry.dependencies]
alive-progress = "3.1.5"
awsipranges = "0.3.3"
azure-identity = "1.16.0"
azure-identity = "1.16.1"
azure-keyvault-keys = "4.9.0"
azure-mgmt-applicationinsights = "4.0.0"
azure-mgmt-authorization = "4.0.0"
@@ -46,13 +46,13 @@ azure-mgmt-storage = "21.1.0"
azure-mgmt-subscription = "3.1.1"
azure-mgmt-web = "7.2.0"
azure-storage-blob = "12.20.0"
boto3 = "1.34.113"
botocore = "1.34.118"
boto3 = "1.34.123"
botocore = "1.34.123"
colorama = "0.4.6"
dash = "2.17.0"
dash-bootstrap-components = "1.6.0"
detect-secrets = "1.5.0"
google-api-python-client = "2.131.0"
google-api-python-client = "2.132.0"
google-auth-httplib2 = ">=0.1,<0.3"
jsonschema = "4.22.0"
kubernetes = "29.0.0"
@@ -66,7 +66,7 @@ python = ">=3.9,<3.13"
pytz = "2024.1"
schema = "0.7.7"
shodan = "1.31.0"
slack-sdk = "3.27.2"
slack-sdk = "3.28.0"
tabulate = "0.9.0"
tzlocal = "5.2"
@@ -87,13 +87,13 @@ mock = "5.1.0"
moto = {extras = ["all"], version = "5.0.9"}
openapi-schema-validator = "0.6.2"
openapi-spec-validator = "0.7.1"
pylint = "3.2.2"
pytest = "8.2.1"
pylint = "3.2.3"
pytest = "8.2.2"
pytest-cov = "5.0.0"
pytest-env = "1.1.3"
pytest-randomly = "3.15.0"
pytest-xdist = "3.6.1"
safety = "3.2.0"
safety = "3.2.3"
vulture = "2.11"
[tool.poetry.group.docs]
+249 -18
View File
@@ -1,3 +1,4 @@
import logging
import os
import pathlib
from unittest import mock
@@ -24,10 +25,12 @@ def mock_prowler_get_latest_release(_, **kwargs):
return response
config_aws = {
old_config_aws = {
"shodan_api_key": None,
"max_security_group_rules": 50,
"max_ec2_instance_age_in_days": 180,
"ec2_allowed_interface_types": ["api_gateway_managed", "vpc_endpoint"],
"ec2_allowed_instance_owners": ["amazon-elb"],
"trusted_account_ids": [],
"log_group_retention_days": 365,
"max_idle_disconnect_timeout_in_seconds": 600,
@@ -59,14 +62,231 @@ config_aws = {
"organizations_enabled_regions": [],
"organizations_trusted_delegated_administrators": [],
"check_rds_instance_replicas": False,
"ec2_allowed_interface_types": [
"api_gateway_managed",
"vpc_endpoint",
],
"days_to_expire_threshold": 7,
}
config_aws = {
"mute_non_default_regions": False,
"max_unused_access_keys_days": 45,
"max_console_access_days": 45,
"shodan_api_key": None,
"max_security_group_rules": 50,
"max_ec2_instance_age_in_days": 180,
"ec2_allowed_interface_types": ["api_gateway_managed", "vpc_endpoint"],
"ec2_allowed_instance_owners": ["amazon-elb"],
"trusted_account_ids": [],
"log_group_retention_days": 365,
"max_idle_disconnect_timeout_in_seconds": 600,
"max_disconnect_timeout_in_seconds": 300,
"max_session_duration_seconds": 36000,
"obsolete_lambda_runtimes": [
"java8",
"go1.x",
"provided",
"python3.6",
"python2.7",
"python3.7",
"nodejs4.3",
"nodejs4.3-edge",
"nodejs6.10",
"nodejs",
"nodejs8.10",
"nodejs10.x",
"nodejs12.x",
"nodejs14.x",
"dotnet5.0",
"dotnetcore1.0",
"dotnetcore2.0",
"dotnetcore2.1",
"dotnetcore3.1",
"ruby2.5",
"ruby2.7",
],
"organizations_enabled_regions": [],
"organizations_trusted_delegated_administrators": [],
"ecr_repository_vulnerability_minimum_severity": "MEDIUM",
"verify_premium_support_plans": True,
"threat_detection_privilege_escalation_threshold": 0.1,
"threat_detection_privilege_escalation_minutes": 1440,
"threat_detection_privilege_escalation_actions": [
"AddPermission",
"AddRoleToInstanceProfile",
"AddUserToGroup",
"AssociateAccessPolicy",
"AssumeRole",
"AttachGroupPolicy",
"AttachRolePolicy",
"AttachUserPolicy",
"ChangePassword",
"CreateAccessEntry",
"CreateAccessKey",
"CreateDevEndpoint",
"CreateEventSourceMapping",
"CreateFunction",
"CreateGroup",
"CreateJob",
"CreateKeyPair",
"CreateLoginProfile",
"CreatePipeline",
"CreatePolicyVersion",
"CreateRole",
"CreateStack",
"DeleteRolePermissionsBoundary",
"DeleteRolePolicy",
"DeleteUserPermissionsBoundary",
"DeleteUserPolicy",
"DetachRolePolicy",
"DetachUserPolicy",
"GetCredentialsForIdentity",
"GetId",
"GetPolicyVersion",
"GetUserPolicy",
"Invoke",
"ModifyInstanceAttribute",
"PassRole",
"PutGroupPolicy",
"PutPipelineDefinition",
"PutRolePermissionsBoundary",
"PutRolePolicy",
"PutUserPermissionsBoundary",
"PutUserPolicy",
"ReplaceIamInstanceProfileAssociation",
"RunInstances",
"SetDefaultPolicyVersion",
"UpdateAccessKey",
"UpdateAssumeRolePolicy",
"UpdateDevEndpoint",
"UpdateEventSourceMapping",
"UpdateFunctionCode",
"UpdateJob",
"UpdateLoginProfile",
],
"threat_detection_enumeration_threshold": 0.1,
"threat_detection_enumeration_minutes": 1440,
"threat_detection_enumeration_actions": [
"DescribeAccessEntry",
"DescribeAccountAttributes",
"DescribeAvailabilityZones",
"DescribeBundleTasks",
"DescribeCarrierGateways",
"DescribeClientVpnRoutes",
"DescribeCluster",
"DescribeDhcpOptions",
"DescribeFlowLogs",
"DescribeImages",
"DescribeInstanceAttribute",
"DescribeInstanceInformation",
"DescribeInstanceTypes",
"DescribeInstances",
"DescribeInstances",
"DescribeKeyPairs",
"DescribeLogGroups",
"DescribeLogStreams",
"DescribeOrganization",
"DescribeRegions",
"DescribeSecurityGroups",
"DescribeSnapshotAttribute",
"DescribeSnapshotTierStatus",
"DescribeSubscriptionFilters",
"DescribeTransitGatewayMulticastDomains",
"DescribeVolumes",
"DescribeVolumesModifications",
"DescribeVpcEndpointConnectionNotifications",
"DescribeVpcs",
"GetAccount",
"GetAccountAuthorizationDetails",
"GetAccountSendingEnabled",
"GetBucketAcl",
"GetBucketLogging",
"GetBucketPolicy",
"GetBucketReplication",
"GetBucketVersioning",
"GetCallerIdentity",
"GetCertificate",
"GetConsoleScreenshot",
"GetCostAndUsage",
"GetDetector",
"GetEbsDefaultKmsKeyId",
"GetEbsEncryptionByDefault",
"GetFindings",
"GetFlowLogsIntegrationTemplate",
"GetIdentityVerificationAttributes",
"GetInstances",
"GetIntrospectionSchema",
"GetLaunchTemplateData",
"GetLaunchTemplateData",
"GetLogRecord",
"GetParameters",
"GetPolicyVersion",
"GetPublicAccessBlock",
"GetQueryResults",
"GetRegions",
"GetSMSAttributes",
"GetSMSSandboxAccountStatus",
"GetSendQuota",
"GetTransitGatewayRouteTableAssociations",
"GetUserPolicy",
"HeadObject",
"ListAccessKeys",
"ListAccounts",
"ListAllMyBuckets",
"ListAssociatedAccessPolicies",
"ListAttachedUserPolicies",
"ListClusters",
"ListDetectors",
"ListDomains",
"ListFindings",
"ListHostedZones",
"ListIPSets",
"ListIdentities",
"ListInstanceProfiles",
"ListObjects",
"ListOrganizationalUnitsForParent",
"ListOriginationNumbers",
"ListPolicyVersions",
"ListRoles",
"ListRoles",
"ListRules",
"ListServiceQuotas",
"ListSubscriptions",
"ListTargetsByRule",
"ListTopics",
"ListUsers",
"LookupEvents",
"Search",
],
"check_rds_instance_replicas": False,
"days_to_expire_threshold": 7,
}
config_azure = {"shodan_api_key": None}
config_azure = {
"shodan_api_key": None,
"php_latest_version": "8.2",
"python_latest_version": "3.12",
"java_latest_version": "17",
}
config_gcp = {"shodan_api_key": None}
config_kubernetes = {
"audit_log_maxbackup": 10,
"audit_log_maxsize": 100,
"audit_log_maxage": 30,
"apiserver_strong_ciphers": [
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
],
"kubelet_strong_ciphers": [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
],
}
class Test_Config:
@@ -131,7 +351,7 @@ class Test_Config:
path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
config_test_file = f"{path}/fixtures/config.yaml"
provider = "aws"
print(load_and_validate_config_file(provider, config_test_file))
assert load_and_validate_config_file(provider, config_test_file) == config_aws
def test_load_and_validate_config_file_gcp(self):
@@ -139,14 +359,17 @@ class Test_Config:
config_test_file = f"{path}/fixtures/config.yaml"
provider = "gcp"
assert load_and_validate_config_file(provider, config_test_file) is None
assert load_and_validate_config_file(provider, config_test_file) == config_gcp
def test_load_and_validate_config_file_kubernetes(self):
path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
config_test_file = f"{path}/fixtures/config.yaml"
provider = "kubernetes"
assert load_and_validate_config_file(provider, config_test_file) is None
print(load_and_validate_config_file(provider, config_test_file))
assert (
load_and_validate_config_file(provider, config_test_file)
== config_kubernetes
)
def test_load_and_validate_config_file_azure(self):
path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
@@ -158,18 +381,22 @@ class Test_Config:
def test_load_and_validate_config_file_old_format(self):
path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
config_test_file = f"{path}/fixtures/config_old.yaml"
assert load_and_validate_config_file("aws", config_test_file) == config_aws
print(load_and_validate_config_file("aws", config_test_file))
assert load_and_validate_config_file("aws", config_test_file) == old_config_aws
assert load_and_validate_config_file("gcp", config_test_file) == {}
assert load_and_validate_config_file("azure", config_test_file) == {}
assert load_and_validate_config_file("kubernetes", config_test_file) == {}
def test_load_and_validate_config_file_invalid_config_file_path(self):
def test_load_and_validate_config_file_invalid_config_file_path(self, caplog):
provider = "aws"
config_file_path = "invalid/path/to/fixer_config.yaml"
with pytest.raises(SystemExit):
load_and_validate_config_file(provider, config_file_path)
with caplog.at_level(logging.ERROR):
result = load_and_validate_config_file(provider, config_file_path)
assert "FileNotFoundError" in caplog.text
assert result == {}
assert pytest is not None
def test_load_and_validate_fixer_config_aws(self):
path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
@@ -199,9 +426,13 @@ class Test_Config:
assert load_and_validate_fixer_config_file(provider, config_test_file) == {}
def test_load_and_validate_fixer_config_invalid_fixer_config_path(self):
def test_load_and_validate_fixer_config_invalid_fixer_config_path(self, caplog):
provider = "aws"
fixer_config_path = "invalid/path/to/fixer_config.yaml"
with pytest.raises(SystemExit):
load_and_validate_fixer_config_file(provider, fixer_config_path)
with caplog.at_level(logging.ERROR):
result = load_and_validate_fixer_config_file(provider, fixer_config_path)
assert "FileNotFoundError" in caplog.text
assert result == {}
assert pytest is not None
+239 -10
View File
@@ -1,9 +1,29 @@
# TODO: UPDATE YAML
# AWS Configuration
aws:
# AWS Global Configuration
# aws.mute_non_default_regions --> Set to True to muted failed findings in non-default regions for AccessAnalyzer, GuardDuty, SecurityHub, DRS and Config
mute_non_default_regions: False
# If you want to mute failed findings only in specific regions, create a file with the following syntax and run it with `prowler aws -w mutelist.yaml`:
# Mutelist:
# Accounts:
# "*":
# Checks:
# "*":
# Regions:
# - "ap-southeast-1"
# - "ap-southeast-2"
# Resources:
# - "*"
# AWS IAM Configuration
# aws.iam_user_accesskey_unused --> CIS recommends 45 days
max_unused_access_keys_days: 45
# aws.iam_user_console_access_unused --> CIS recommends 45 days
max_console_access_days: 45
# AWS EC2 Configuration
# aws.ec2_elastic_ip_shodan
# TODO: create common config
shodan_api_key: null
# aws.ec2_securitygroup_with_many_ingress_egress_rules --> by default is 50 rules
max_security_group_rules: 50
@@ -13,16 +33,15 @@ aws:
# allowed network interface types for security groups open to the Internet
ec2_allowed_interface_types:
[
"api_gateway_managed",
"vpc_endpoint",
"api_gateway_managed",
"vpc_endpoint",
]
# allowed network interface owners for security groups open to the Internet
ec2_allowed_instance_owners:
[
"amazon-elb"
"amazon-elb"
]
# AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries)
# Single account environment: No action required. The AWS account number will be automatically added by the checks.
# Multi account environment: Any additional trusted account number should be added as a space separated list, e.g.
@@ -69,27 +88,237 @@ aws:
]
# AWS Organizations
# organizations_scp_check_deny_regions
# organizations_enabled_regions: [
# 'eu-central-1',
# 'eu-west-1',
# aws.organizations_scp_check_deny_regions
# aws.organizations_enabled_regions: [
# "eu-central-1",
# "eu-west-1",
# "us-east-1"
# ]
organizations_enabled_regions: []
organizations_trusted_delegated_administrators: []
# AWS ECR
# aws.ecr_repositories_scan_vulnerabilities_in_latest_image
# CRITICAL
# HIGH
# MEDIUM
ecr_repository_vulnerability_minimum_severity: "MEDIUM"
# AWS Trusted Advisor
# aws.trustedadvisor_premium_support_plan_subscribed
verify_premium_support_plans: True
# AWS CloudTrail Configuration
# aws.cloudtrail_threat_detection_privilege_escalation
threat_detection_privilege_escalation_threshold: 0.1 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.1 (10%)
threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours)
threat_detection_privilege_escalation_actions:
[
"AddPermission",
"AddRoleToInstanceProfile",
"AddUserToGroup",
"AssociateAccessPolicy",
"AssumeRole",
"AttachGroupPolicy",
"AttachRolePolicy",
"AttachUserPolicy",
"ChangePassword",
"CreateAccessEntry",
"CreateAccessKey",
"CreateDevEndpoint",
"CreateEventSourceMapping",
"CreateFunction",
"CreateGroup",
"CreateJob",
"CreateKeyPair",
"CreateLoginProfile",
"CreatePipeline",
"CreatePolicyVersion",
"CreateRole",
"CreateStack",
"DeleteRolePermissionsBoundary",
"DeleteRolePolicy",
"DeleteUserPermissionsBoundary",
"DeleteUserPolicy",
"DetachRolePolicy",
"DetachUserPolicy",
"GetCredentialsForIdentity",
"GetId",
"GetPolicyVersion",
"GetUserPolicy",
"Invoke",
"ModifyInstanceAttribute",
"PassRole",
"PutGroupPolicy",
"PutPipelineDefinition",
"PutRolePermissionsBoundary",
"PutRolePolicy",
"PutUserPermissionsBoundary",
"PutUserPolicy",
"ReplaceIamInstanceProfileAssociation",
"RunInstances",
"SetDefaultPolicyVersion",
"UpdateAccessKey",
"UpdateAssumeRolePolicy",
"UpdateDevEndpoint",
"UpdateEventSourceMapping",
"UpdateFunctionCode",
"UpdateJob",
"UpdateLoginProfile",
]
# aws.cloudtrail_threat_detection_enumeration
threat_detection_enumeration_threshold: 0.1 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.1 (10%)
threat_detection_enumeration_minutes: 1440 # Past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours)
threat_detection_enumeration_actions:
[
"DescribeAccessEntry",
"DescribeAccountAttributes",
"DescribeAvailabilityZones",
"DescribeBundleTasks",
"DescribeCarrierGateways",
"DescribeClientVpnRoutes",
"DescribeCluster",
"DescribeDhcpOptions",
"DescribeFlowLogs",
"DescribeImages",
"DescribeInstanceAttribute",
"DescribeInstanceInformation",
"DescribeInstanceTypes",
"DescribeInstances",
"DescribeInstances",
"DescribeKeyPairs",
"DescribeLogGroups",
"DescribeLogStreams",
"DescribeOrganization",
"DescribeRegions",
"DescribeSecurityGroups",
"DescribeSnapshotAttribute",
"DescribeSnapshotTierStatus",
"DescribeSubscriptionFilters",
"DescribeTransitGatewayMulticastDomains",
"DescribeVolumes",
"DescribeVolumesModifications",
"DescribeVpcEndpointConnectionNotifications",
"DescribeVpcs",
"GetAccount",
"GetAccountAuthorizationDetails",
"GetAccountSendingEnabled",
"GetBucketAcl",
"GetBucketLogging",
"GetBucketPolicy",
"GetBucketReplication",
"GetBucketVersioning",
"GetCallerIdentity",
"GetCertificate",
"GetConsoleScreenshot",
"GetCostAndUsage",
"GetDetector",
"GetEbsDefaultKmsKeyId",
"GetEbsEncryptionByDefault",
"GetFindings",
"GetFlowLogsIntegrationTemplate",
"GetIdentityVerificationAttributes",
"GetInstances",
"GetIntrospectionSchema",
"GetLaunchTemplateData",
"GetLaunchTemplateData",
"GetLogRecord",
"GetParameters",
"GetPolicyVersion",
"GetPublicAccessBlock",
"GetQueryResults",
"GetRegions",
"GetSMSAttributes",
"GetSMSSandboxAccountStatus",
"GetSendQuota",
"GetTransitGatewayRouteTableAssociations",
"GetUserPolicy",
"HeadObject",
"ListAccessKeys",
"ListAccounts",
"ListAllMyBuckets",
"ListAssociatedAccessPolicies",
"ListAttachedUserPolicies",
"ListClusters",
"ListDetectors",
"ListDomains",
"ListFindings",
"ListHostedZones",
"ListIPSets",
"ListIdentities",
"ListInstanceProfiles",
"ListObjects",
"ListOrganizationalUnitsForParent",
"ListOriginationNumbers",
"ListPolicyVersions",
"ListRoles",
"ListRoles",
"ListRules",
"ListServiceQuotas",
"ListSubscriptions",
"ListTargetsByRule",
"ListTopics",
"ListUsers",
"LookupEvents",
"Search",
]
# AWS RDS Configuration
# aws.rds_instance_backup_enabled
# Whether to check RDS instance replicas or not
check_rds_instance_replicas: False
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
# Azure Configuration
azure:
# Azure Network Configuration
# azure.network_public_ip_shodan
# TODO: create common config
shodan_api_key: null
# Azure App Service
# azure.app_ensure_php_version_is_latest
php_latest_version: "8.2"
# azure.app_ensure_python_version_is_latest
python_latest_version: "3.12"
# azure.app_ensure_java_version_is_latest
java_latest_version: "17"
# GCP Configuration
gcp:
# GCP Compute Configuration
# gcp.compute_public_address_shodan
shodan_api_key: null
# Kubernetes Configuration
kubernetes:
# Kubernetes API Server
# kubernetes.apiserver_audit_log_maxbackup_set
audit_log_maxbackup: 10
# kubernetes.apiserver_audit_log_maxsize_set
audit_log_maxsize: 100
# kubernetes.apiserver_audit_log_maxage_set
audit_log_maxage: 30
# kubernetes.apiserver_strong_ciphers_only
apiserver_strong_ciphers:
[
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
]
# Kubelet
# kubernetes.kubelet_strong_ciphers_only
kubelet_strong_ciphers:
[
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
]
+4
View File
@@ -76,3 +76,7 @@ organizations_trusted_delegated_administrators: []
# aws.rds_instance_backup_enabled
# Whether to check RDS instance replicas or not
check_rds_instance_replicas: False
# AWS ACM Configuration
# aws.acm_certificates_expiration_check
days_to_expire_threshold: 7
+138 -11
View File
@@ -5,30 +5,36 @@ import boto3
from mock import MagicMock
from moto import mock_aws
from prowler.config.config import csv_file_suffix
from prowler.config.config import (
csv_file_suffix,
html_file_suffix,
json_asff_file_suffix,
json_ocsf_file_suffix,
)
from prowler.providers.aws.lib.s3.s3 import get_s3_object_path, send_to_s3_bucket
AWS_ACCOUNT_ID = "123456789012"
AWS_REGION = "us-east-1"
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1
ACTUAL_DIRECTORY = Path(path.dirname(path.realpath(__file__)))
FIXTURES_DIR_NAME = "fixtures"
S3_BUCKET_NAME = "test_bucket"
OUTPUT_MODE_CSV = "csv"
OUTPUT_MODE_JSON_OCSF = "json-ocsf"
OUTPUT_MODE_JSON_ASFF = "json-asff"
OUTPUT_MODE_HTML = "html"
OUTPUT_MODE_CIS_1_4_AWS = "cis_1.4_aws"
class TestS3:
@mock_aws
def test_send_to_s3_bucket(self):
def test_send_to_s3_bucket_csv(self):
# Mock Audit Info
provider = MagicMock()
# Create mock session
provider.current_session = boto3.session.Session(region_name=AWS_REGION)
provider.identity.account = AWS_ACCOUNT_ID
provider.current_session = boto3.session.Session(
region_name=AWS_REGION_US_EAST_1
)
provider.identity.account = AWS_ACCOUNT_NUMBER
# Create mock bucket
client = provider.current_session.client("s3")
@@ -60,14 +66,135 @@ class TestS3:
== "binary/octet-stream"
)
@mock_aws
def test_send_to_s3_bucket_json_ocsf(self):
# Mock Audit Info
provider = MagicMock()
# Create mock session
provider.current_session = boto3.session.Session(
region_name=AWS_REGION_US_EAST_1
)
provider.identity.account = AWS_ACCOUNT_NUMBER
# Create mock bucket
client = provider.current_session.client("s3")
client.create_bucket(Bucket=S3_BUCKET_NAME)
# Mocked CSV output file
output_directory = f"{ACTUAL_DIRECTORY}/{FIXTURES_DIR_NAME}"
filename = f"prowler-output-{provider.identity.account}"
# Send mock CSV file to mock S3 Bucket
send_to_s3_bucket(
filename,
output_directory,
OUTPUT_MODE_JSON_OCSF,
S3_BUCKET_NAME,
provider.current_session,
)
bucket_directory = get_s3_object_path(output_directory)
object_name = f"{bucket_directory}/{OUTPUT_MODE_JSON_OCSF}/{filename}{json_ocsf_file_suffix}"
assert (
client.get_object(
Bucket=S3_BUCKET_NAME,
Key=object_name,
)["ContentType"]
== "binary/octet-stream"
)
@mock_aws
def test_send_to_s3_bucket_json_asff(self):
# Mock Audit Info
provider = MagicMock()
# Create mock session
provider.current_session = boto3.session.Session(
region_name=AWS_REGION_US_EAST_1
)
provider.identity.account = AWS_ACCOUNT_NUMBER
# Create mock bucket
client = provider.current_session.client("s3")
client.create_bucket(Bucket=S3_BUCKET_NAME)
# Mocked CSV output file
output_directory = f"{ACTUAL_DIRECTORY}/{FIXTURES_DIR_NAME}"
filename = f"prowler-output-{provider.identity.account}"
# Send mock CSV file to mock S3 Bucket
send_to_s3_bucket(
filename,
output_directory,
OUTPUT_MODE_JSON_ASFF,
S3_BUCKET_NAME,
provider.current_session,
)
bucket_directory = get_s3_object_path(output_directory)
object_name = f"{bucket_directory}/{OUTPUT_MODE_JSON_ASFF}/{filename}{json_asff_file_suffix}"
assert (
client.get_object(
Bucket=S3_BUCKET_NAME,
Key=object_name,
)["ContentType"]
== "binary/octet-stream"
)
@mock_aws
def test_send_to_s3_bucket_html(self):
# Mock Audit Info
provider = MagicMock()
# Create mock session
provider.current_session = boto3.session.Session(
region_name=AWS_REGION_US_EAST_1
)
provider.identity.account = AWS_ACCOUNT_NUMBER
# Create mock bucket
client = provider.current_session.client("s3")
client.create_bucket(Bucket=S3_BUCKET_NAME)
# Mocked CSV output file
output_directory = f"{ACTUAL_DIRECTORY}/{FIXTURES_DIR_NAME}"
filename = f"prowler-output-{provider.identity.account}"
# Send mock CSV file to mock S3 Bucket
send_to_s3_bucket(
filename,
output_directory,
OUTPUT_MODE_HTML,
S3_BUCKET_NAME,
provider.current_session,
)
bucket_directory = get_s3_object_path(output_directory)
object_name = (
f"{bucket_directory}/{OUTPUT_MODE_HTML}/{filename}{html_file_suffix}"
)
assert (
client.get_object(
Bucket=S3_BUCKET_NAME,
Key=object_name,
)["ContentType"]
== "binary/octet-stream"
)
@mock_aws
def test_send_to_s3_bucket_compliance(self):
# Mock Audit Info
provider = MagicMock()
# Create mock session
provider.current_session = boto3.session.Session(region_name=AWS_REGION)
provider.identity.account = AWS_ACCOUNT_ID
provider.current_session = boto3.session.Session(
region_name=AWS_REGION_US_EAST_1
)
provider.identity.account = AWS_ACCOUNT_NUMBER
# Create mock bucket
client = provider.current_session.client("s3")
@@ -33,6 +33,7 @@ class Test_acm_certificates_expiration_check:
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
expiration_days = 5
in_use = True
acm_client = mock.MagicMock
acm_client.certificates = [
@@ -42,11 +43,14 @@ class Test_acm_certificates_expiration_check:
name=certificate_name,
type=certificate_type,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
region=AWS_REGION,
)
]
acm_client.audit_config = {"days_to_expire_threshold": 7}
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
@@ -76,6 +80,7 @@ class Test_acm_certificates_expiration_check:
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
expiration_days = -400
in_use = True
acm_client = mock.MagicMock
acm_client.certificates = [
@@ -85,16 +90,18 @@ class Test_acm_certificates_expiration_check:
name=certificate_name,
type=certificate_type,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
region=AWS_REGION,
)
]
acm_client.audit_config = {"days_to_expire_threshold": 7}
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
):
# Test Check
from prowler.providers.aws.services.acm.acm_certificates_expiration_check.acm_certificates_expiration_check import (
acm_certificates_expiration_check,
)
@@ -119,6 +126,7 @@ class Test_acm_certificates_expiration_check:
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
expiration_days = 365
in_use = True
acm_client = mock.MagicMock
acm_client.certificates = [
@@ -128,16 +136,18 @@ class Test_acm_certificates_expiration_check:
name=certificate_name,
type=certificate_type,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
region=AWS_REGION,
)
]
acm_client.audit_config = {"days_to_expire_threshold": 7}
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
):
# Test Check
from prowler.providers.aws.services.acm.acm_certificates_expiration_check.acm_certificates_expiration_check import (
acm_certificates_expiration_check,
)
@@ -155,3 +165,90 @@ class Test_acm_certificates_expiration_check:
assert result[0].resource_arn == certificate_arn
assert result[0].region == AWS_REGION
assert result[0].resource_tags == []
def test_acm_certificate_not_in_use(self):
certificate_id = str(uuid.uuid4())
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
expiration_days = 365
in_use = False
acm_client = mock.MagicMock
acm_client.certificates = [
Certificate(
arn=certificate_arn,
id=certificate_id,
name=certificate_name,
type=certificate_type,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
region=AWS_REGION,
)
]
acm_client.audit_config = {"days_to_expire_threshold": 7}
acm_client.provider = mock.MagicMock(scan_unused_services=False)
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
):
from prowler.providers.aws.services.acm.acm_certificates_expiration_check.acm_certificates_expiration_check import (
acm_certificates_expiration_check,
)
check = acm_certificates_expiration_check()
result = check.execute()
assert len(result) == 0
def test_acm_certificate_not_in_use_expired_scan_unused_services(self):
certificate_id = str(uuid.uuid4())
certificate_arn = f"arn:aws:acm:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:certificate/{certificate_id}"
certificate_name = "test-certificate.com"
certificate_type = "AMAZON_ISSUED"
expiration_days = -400
in_use = False
acm_client = mock.MagicMock
acm_client.certificates = [
Certificate(
arn=certificate_arn,
id=certificate_id,
name=certificate_name,
type=certificate_type,
expiration_days=expiration_days,
in_use=in_use,
transparency_logging=True,
region=AWS_REGION,
)
]
acm_client.audit_config = {"days_to_expire_threshold": 7}
acm_client.provider = mock.MagicMock(scan_unused_services=True)
with mock.patch(
"prowler.providers.aws.services.acm.acm_service.ACM",
new=acm_client,
):
from prowler.providers.aws.services.acm.acm_certificates_expiration_check.acm_certificates_expiration_check import (
acm_certificates_expiration_check,
)
check = acm_certificates_expiration_check()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"ACM Certificate {certificate_id} for {certificate_name} has expired ({abs(expiration_days)} days ago)."
)
assert result[0].resource_id == certificate_id
assert result[0].resource_arn == certificate_arn
assert result[0].region == AWS_REGION
assert result[0].resource_tags == []
@@ -41,6 +41,7 @@ class Test_acm_certificates_transparency_logs_enabled:
type=certificate_type,
expiration_days=365,
transparency_logging=True,
in_use=True,
region=AWS_REGION,
)
]
@@ -83,6 +84,7 @@ class Test_acm_certificates_transparency_logs_enabled:
type=certificate_type,
expiration_days=365,
transparency_logging=False,
in_use=True,
region=AWS_REGION,
)
]
@@ -12,13 +12,15 @@ AWS_REGION = "us-east-1"
class Test_iam_user_console_access_unused_test:
@mock_aws
def test_iam_user_logged_45_days(self):
def test_iam_user_logged_2_days_ago(self):
password_last_used = (
datetime.datetime.now() - datetime.timedelta(days=2)
).strftime("%Y-%m-%d %H:%M:%S+00:00")
iam_client = client("iam")
user = "test-user"
arn = iam_client.create_user(UserName=user)["User"]["Arn"]
# Enable console access
iam_client.create_login_profile(UserName=user, Password="Test1234")
from prowler.providers.aws.services.iam.iam_service import IAM
@@ -59,6 +61,7 @@ class Test_iam_user_console_access_unused_test:
iam_client = client("iam")
user = "test-user"
arn = iam_client.create_user(UserName=user)["User"]["Arn"]
iam_client.create_login_profile(UserName=user, Password="Test1234")
from prowler.providers.aws.services.iam.iam_service import IAM
@@ -116,14 +119,14 @@ class Test_iam_user_console_access_unused_test:
)
service_client.users[0].password_last_used = ""
# raise Exception
check = iam_user_console_access_unused()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"User {user} does not have a console password or is unused."
== f"User {user} does not have console access enabled or is unused."
)
assert result[0].resource_id == user
assert result[0].resource_arn == arn
@@ -0,0 +1,351 @@
from unittest import mock
import botocore
from boto3 import client
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
make_api_call = botocore.client.BaseClient._make_api_call
rds_account_arn = f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:account"
class Test_rds_instance__no_event_subscriptions:
@mock_aws
def test_rds_no_events(self):
from prowler.providers.aws.services.rds.rds_service import RDS
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups import (
rds_instance_event_subscription_security_groups,
)
check = rds_instance_event_subscription_security_groups()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS security group event categories of configuration change and failure are not subscribed."
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
assert result[0].resource_arn == rds_account_arn
@mock_aws
def test_rds_no_events_ignoring(self):
from prowler.providers.aws.services.rds.rds_service import RDS
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
aws_provider._scan_unused_services = False
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups import (
rds_instance_event_subscription_security_groups,
)
check = rds_instance_event_subscription_security_groups()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_rds_security_event_subscription_enabled(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_parameter_group(
DBParameterGroupName="test",
DBParameterGroupFamily="default.aurora-postgresql14",
Description="test parameter group",
)
conn.create_db_instance(
DBInstanceIdentifier="db-master-1",
AllocatedStorage=10,
Engine="aurora-postgresql",
DBName="aurora-postgres",
DBInstanceClass="db.m1.small",
DBParameterGroupName="test",
DBClusterIdentifier="db-cluster-1",
)
conn.create_event_subscription(
SubscriptionName="TestSub",
SnsTopicArn=f"arn:aws:sns:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:test",
SourceType="db-security-group",
Enabled=True,
Tags=[
{"Key": "test", "Value": "testing"},
],
)
from prowler.providers.aws.services.rds.rds_service import RDS
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups import (
rds_instance_event_subscription_security_groups,
)
check = rds_instance_event_subscription_security_groups()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "RDS security group events are subscribed."
)
assert result[0].resource_id == "TestSub"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:es:TestSub"
)
@mock_aws
def test_rds_security_event_failure_only_subscription(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_parameter_group(
DBParameterGroupName="test",
DBParameterGroupFamily="default.aurora-postgresql14",
Description="test parameter group",
)
conn.create_db_instance(
DBInstanceIdentifier="db-master-1",
AllocatedStorage=10,
Engine="aurora-postgresql",
DBName="aurora-postgres",
DBInstanceClass="db.m1.small",
DBParameterGroupName="test",
DBClusterIdentifier="db-cluster-1",
)
conn.create_event_subscription(
SubscriptionName="TestSub",
SnsTopicArn=f"arn:aws:sns:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:test",
SourceType="db-security-group",
EventCategories=["failure"],
Enabled=True,
Tags=[
{"Key": "test", "Value": "testing"},
],
)
from prowler.providers.aws.services.rds.rds_service import RDS
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups import (
rds_instance_event_subscription_security_groups,
)
check = rds_instance_event_subscription_security_groups()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS security group event category of configuration change is not subscribed."
)
assert result[0].resource_id == "TestSub"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:es:TestSub"
)
assert result[0].resource_tags == []
@mock_aws
def test_rds_security_event_configuration_change_only_subscription(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_parameter_group(
DBParameterGroupName="test",
DBParameterGroupFamily="default.aurora-postgresql14",
Description="test parameter group",
)
conn.create_db_instance(
DBInstanceIdentifier="db-master-1",
AllocatedStorage=10,
Engine="aurora-postgresql",
DBName="aurora-postgres",
DBInstanceClass="db.m1.small",
DBParameterGroupName="test",
DBClusterIdentifier="db-cluster-1",
)
conn.create_event_subscription(
SubscriptionName="TestSub",
SnsTopicArn=f"arn:aws:sns:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:test",
SourceType="db-security-group",
EventCategories=["configuration change"],
Enabled=True,
)
from prowler.providers.aws.services.rds.rds_service import RDS
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups import (
rds_instance_event_subscription_security_groups,
)
check = rds_instance_event_subscription_security_groups()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS security group event category of failure is not subscribed."
)
assert result[0].resource_id == "TestSub"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:es:TestSub"
)
@mock_aws
def test_rds_no_security_group_event_subscription(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_parameter_group(
DBParameterGroupName="test",
DBParameterGroupFamily="default.aurora-postgresql14",
Description="test parameter group",
)
conn.create_db_instance(
DBInstanceIdentifier="db-master-1",
AllocatedStorage=10,
Engine="aurora-postgresql",
DBName="aurora-postgres",
DBInstanceClass="db.m1.small",
DBParameterGroupName="test",
DBClusterIdentifier="db-cluster-1",
)
conn.create_event_subscription(
SubscriptionName="TestSub",
SnsTopicArn=f"arn:aws:sns:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:test",
SourceType="db-instance",
EventCategories=["configuration change"],
Enabled=True,
)
from prowler.providers.aws.services.rds.rds_service import RDS
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups import (
rds_instance_event_subscription_security_groups,
)
check = rds_instance_event_subscription_security_groups()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS security group event categories of configuration change and failure are not subscribed."
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
assert result[0].resource_arn == rds_account_arn
@mock_aws
def test_rds_no_event_subscription(self):
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_parameter_group(
DBParameterGroupName="test",
DBParameterGroupFamily="default.aurora-postgresql14",
Description="test parameter group",
)
conn.create_db_instance(
DBInstanceIdentifier="db-master-1",
AllocatedStorage=10,
Engine="aurora-postgresql",
DBName="aurora-postgres",
DBInstanceClass="db.m1.small",
DBParameterGroupName="test",
DBClusterIdentifier="db-cluster-1",
)
from prowler.providers.aws.services.rds.rds_service import RDS
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups.rds_client",
new=RDS(aws_provider),
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_event_subscription_security_groups.rds_instance_event_subscription_security_groups import (
rds_instance_event_subscription_security_groups,
)
check = rds_instance_event_subscription_security_groups()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS security group event categories of configuration change and failure are not subscribed."
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == AWS_ACCOUNT_NUMBER
assert result[0].resource_arn == rds_account_arn
@@ -288,7 +288,39 @@ class Test_RDS_Service:
assert rds.db_cluster_snapshots[0].region == AWS_REGION_US_EAST_1
assert not rds.db_cluster_snapshots[0].public
# Test RDS describe db engine versions
# Test RDS describe db event subscriptions
@mock_aws
def test__describe_db_event_subscriptions_(self):
# RDS client for this test class
conn = client("rds", region_name=AWS_REGION_US_EAST_1)
conn.create_db_instance(
DBInstanceIdentifier="db-primary-1",
AllocatedStorage=10,
Engine="postgres",
DBName="staging-postgres",
DBInstanceClass="db.m1.small",
)
conn.create_event_subscription(
SubscriptionName="TestSub",
SnsTopicArn=f"arn:aws:sns:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:test",
SourceType="db-security-group",
Enabled=True,
Tags=[
{"Key": "test", "Value": "testing"},
],
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
rds = RDS(aws_provider)
assert len(rds.db_event_subscriptions) == 1
assert (
rds.db_event_subscriptions[0].sns_topic_arn
== f"arn:aws:sns:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:test"
)
assert rds.db_event_subscriptions[0].enabled
assert rds.db_event_subscriptions[0].region == AWS_REGION_US_EAST_1
assert rds.db_event_subscriptions[0].source_type == "db-security-group"
# Test RDS engine version
@mock_aws
def test__describe_db_engine_versions__(self):
# RDS client for this test class
@@ -3,6 +3,7 @@ from unittest import mock
from prowler.providers.azure.services.network.network_service import NetworkWatcher
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
AZURE_SUBSCRIPTION_NAME,
set_mocked_azure_provider,
)
@@ -36,12 +37,13 @@ class Test_network_watcher_enabled:
def test_network_invalid_network_watchers(self):
network_client = mock.MagicMock
locations = ["location"]
network_client.locations = {AZURE_SUBSCRIPTION_ID: locations}
network_client.locations = {AZURE_SUBSCRIPTION_NAME: locations}
network_client.subscriptions = {AZURE_SUBSCRIPTION_NAME: AZURE_SUBSCRIPTION_ID}
network_watcher_name = "Network Watcher"
network_watcher_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Network/networkWatchers/{locations[0]}"
network_watcher_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/NetworkWatcherRG/providers/Microsoft.Network/networkWatchers/NetworkWatcher_*"
network_client.network_watchers = {
AZURE_SUBSCRIPTION_ID: [
AZURE_SUBSCRIPTION_NAME: [
NetworkWatcher(
id=network_watcher_id,
name=network_watcher_name,
@@ -71,22 +73,23 @@ class Test_network_watcher_enabled:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Network Watcher is not enabled for the location {locations[0]} in subscription {AZURE_SUBSCRIPTION_ID}."
== f"Network Watcher is not enabled for the following locations in subscription '{AZURE_SUBSCRIPTION_NAME}': location."
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].subscription == AZURE_SUBSCRIPTION_NAME
assert result[0].resource_name == network_watcher_name
assert result[0].resource_id == network_watcher_id
assert result[0].location == "location"
assert result[0].location == "Global"
def test_network_valid_network_watchers(self):
network_client = mock.MagicMock
locations = ["location"]
network_client.locations = {AZURE_SUBSCRIPTION_ID: locations}
network_client.locations = {AZURE_SUBSCRIPTION_NAME: locations}
network_client.subscriptions = {AZURE_SUBSCRIPTION_NAME: AZURE_SUBSCRIPTION_ID}
network_watcher_name = "Network Watcher"
network_watcher_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/providers/Microsoft.Network/networkWatchers/{locations[0]}"
network_watcher_id = f"/subscriptions/{AZURE_SUBSCRIPTION_ID}/resourceGroups/NetworkWatcherRG/providers/Microsoft.Network/networkWatchers/NetworkWatcher_*"
network_client.network_watchers = {
AZURE_SUBSCRIPTION_ID: [
AZURE_SUBSCRIPTION_NAME: [
NetworkWatcher(
id=network_watcher_id,
name=network_watcher_name,
@@ -116,9 +119,9 @@ class Test_network_watcher_enabled:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Network Watcher is enabled for the location {locations[0]} in subscription {AZURE_SUBSCRIPTION_ID}."
== f"Network Watcher is enabled for all locations in subscription '{AZURE_SUBSCRIPTION_NAME}'."
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].subscription == AZURE_SUBSCRIPTION_NAME
assert result[0].resource_name == network_watcher_name
assert result[0].resource_id == network_watcher_id
assert result[0].location == "location"
assert result[0].location == "Global"