Compare commits

..

236 Commits

Author SHA1 Message Date
MarioRgzLpz
185d6c482d feat(opensearch): Add check logic with respective unit tests. Add metadata too 2024-10-10 19:56:36 +02:00
MarioRgzLpz
04993f4e76 feat(opensearch): Add new attribute to Domain model in the opensearch service and a test for that change 2024-10-10 19:55:01 +02:00
MarioRgzLpz
2bd9f73c1b refactor(opensearch): Adjust all checks to change from list to dict and change tests to use moto instead of magicMock 2024-10-10 19:12:14 +02:00
MarioRgzLpz
0d743fa179 feat(opensearch): Add check logic with respective unit tests and metadata 2024-10-10 19:06:34 +02:00
MarioRgzLpz
eca94e2e3b feat(opensearch): Change service adding two new attributes to Domain model for new check and to use getters in describe method, use moto in service tests and assert new atributtes 2024-10-10 19:01:35 +02:00
Rubén De la Torre Vico
0c149461b3 chore(sns): manage ResourceNotFoundException and add paralelism (#5345) 2024-10-09 08:56:39 -04:00
Pedro Martín
3ee39cff2a feat(scan): execute all checks if no checks are provided (#5307) 2024-10-09 11:46:38 +02:00
Pedro Martín
41ba118cc4 feat(scan): add scan duration (#5305)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
Co-authored-by: Prowler Bot <bot@prowler.com>
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
Co-authored-by: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com>
2024-10-09 11:12:39 +02:00
Sergio Garcia
e0587fe0cf fix(Dockerfile): install git dependency (#5339) 2024-10-09 08:58:55 +02:00
Daniel Barranquero
50481665ce feat(redshift): add new check redshift_cluster_in_transit_encryption_enabled (#5271)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-08 14:15:32 -04:00
Prowler Bot
a49c744e08 chore(regions_update): Changes in regions for AWS services (#5323)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-08 14:13:17 -04:00
Rubén De la Torre Vico
aa32634105 chore(guardduty): mock failing tests using moto (#5334) 2024-10-08 13:27:37 -04:00
Rubén De la Torre Vico
b27898de1d chore(ecs): mock all tests using moto (#5326) 2024-10-08 12:11:33 -04:00
Sergio Garcia
b703357027 chore(secrets): use master branch of Yelp/detect-secrets (#5298)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-10-08 09:55:46 -04:00
Rubén De la Torre Vico
27cd9b22df feat(guardduty): add new check guardduty_lambda_protection_enabled (#5299)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-08 08:20:23 -04:00
Pepe Fagoaga
5bf85366e0 chore(secrets): Add TelegramBotToken detector (#5321) 2024-10-08 08:09:26 -04:00
dependabot[bot]
30bc971f4b chore(deps): bump trufflesecurity/trufflehog from 3.82.6 to 3.82.7 (#5315)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 10:44:56 +02:00
Sergio Garcia
3950d7eba8 fix(threat detection): ignore AWS services events (#5276) 2024-10-07 14:25:09 -04:00
Rubén De la Torre Vico
2f8a3d2ef8 feat(guardduty): add new check guardduty_ec2_malware_protection_enabled (#5297)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-07 13:03:36 -04:00
Prowler Bot
3b64bbd3a8 chore(regions_update): Changes in regions for AWS services (#5302)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-10-07 10:58:40 -04:00
Hugo Pereira Brito
09d099891a feat(wafv2): change web_acls from list to dict (#5308)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-07 10:23:58 -04:00
Mario Rodriguez Lopez
a6b10a8611 feat(efs): add new check efs_access_point_enforce_user_identity (#5285)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-04 15:16:10 -04:00
Lefteris
c239ede3f9 feat(glue): add check glue_ml_transform_encrypted_at_rest (#5272)
Co-authored-by: Lefteris Gilmaz <lefterisgilmaz@Lefteriss-MacBook-Pro.local>
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-04 14:13:11 -04:00
Hugo Pereira Brito
66f2754017 feat(networkfirewall): add new check networkfirewall_policy_default_action_full_packets (#5284)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-04 14:00:25 -04:00
Hugo Pereira Brito
9138ecdce9 feat(kinesis): add new check kinesis_stream_encrypted_at_rest (#5292)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-04 13:59:49 -04:00
Rubén De la Torre Vico
2b66368cf2 feat(guardduty): add new check guardduty_eks_audit_log_enabled (#5293)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-04 13:43:04 -04:00
Mario Rodriguez Lopez
aa3425a7de feat(efs): add new check efs_access_point_enforce_root_directory (#5277)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-04 13:12:47 -04:00
Mario Rodriguez Lopez
a31b15c26c feat(efs): add new check efs_mount_target_not_publicly_accesible (#5275)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-04 11:41:51 -04:00
Hugo Pereira Brito
f2301d5ed6 feat(networkfirewall): add new check networkfirewall_policy_default_action_fragmented_packets (#5244)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-04 11:41:26 -04:00
Rubén De la Torre Vico
df10253056 chore(cloudwatch): Improve checks related with function check_cloudwatch_log_metric_filter (#5286) 2024-10-04 11:18:46 -04:00
Sergio Garcia
d5acdc766a chore(ocsf): adapt mapping for version 1.3.0 (#5287) 2024-10-04 10:59:51 -04:00
Rubén De la Torre Vico
e389e0136f chore(cloudwatch): add tags to missing checks report (#5261)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-03 18:04:43 -04:00
Rubén De la Torre Vico
8bb3bd0dcb chore(iam): add tags to missing checks report (#5280)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-03 13:47:10 -04:00
Hugo Pereira Brito
4d4bf3fa11 feat(networkfirewall): add new check networkfirewall_multi_az (#5247)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-03 13:46:44 -04:00
Daniel Barranquero
e99c58405c feat(redshift): add new check redshift_cluster_non_default_database_name (#5283)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-03 11:28:54 -04:00
Daniel Barranquero
2177704b4b feat(redshift): add new check redshift_cluster_encrypted_at_rest (#5262)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-02 17:06:19 -04:00
Mario Rodriguez Lopez
2ffe7f3ef7 feat(ecs): add new check ecs_service_fargate_latest_platform_version (#5258)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-02 16:50:20 -04:00
dependabot[bot]
158263a8bf chore(deps-dev): bump moto from 5.0.15 to 5.0.16 (#5256)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-02 15:40:34 -04:00
Daniel Barranquero
469986dd28 feat(redshift): add new check redshift_cluster_non_default_username (#5268) 2024-10-02 13:54:12 -04:00
Hugo Pereira Brito
ff101087bf feat(networkfirewall): add new check networkfirewall_logging_enabled (#5145)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-02 12:09:13 -04:00
dependabot[bot]
b2151e2e9c chore(deps): bump boto3 from 1.35.28 to 1.35.29 (#5257)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-02 11:27:39 -04:00
Sergio Garcia
2c4244b1fb chore(version): update Prowler version (#5251) 2024-10-02 11:14:26 -04:00
Hugo Pereira Brito
260cdf575a feat(kinesis): add new service Kinesis (#5228)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-10-02 10:59:59 -04:00
Michael St.Onge
ab4190c215 chore(contrib): update aws-multi-account-securityhub deployment (#5263) 2024-10-02 10:58:02 -04:00
Mario Rodriguez Lopez
7f97b0a57f feat(ecs): Ensure ECS clusters use Container Insights (#5241)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-02 10:42:52 -04:00
Daniel Barranquero
2c2dd82d0c feat(dynamodb): add new check dynamodb_table_autoscaling_enabled (#5129)
Co-authored-by: Sergio <sergio@prowler.com>
2024-10-02 10:42:36 -04:00
Mario Rodriguez Lopez
2511df1732 fix(ecs): Adjust code to the new ARN formats in the ECS service (#5259) 2024-10-02 09:40:32 -04:00
Rubén De la Torre Vico
f955dd76d9 test(aws): fix failing tests for ecs_task_definitions_logging_enabled and ssm_managed_compliant_patching (#5267) 2024-10-02 09:35:27 -04:00
Prowler Bot
a08cc769c8 chore(regions_update): Changes in regions for AWS services (#5269)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-10-02 08:50:30 -04:00
Prowler Bot
77ac5e3b91 chore(regions_update): Changes in regions for AWS services (#5260)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-10-01 14:10:38 -04:00
dependabot[bot]
2da8f2b1eb chore(deps-dev): bump mkdocs-material from 9.5.38 to 9.5.39 (#5255)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-01 14:10:17 -04:00
Sergio Garcia
38e024216c chore(ec2): enhance security group with any open port check (#5215) 2024-09-30 14:53:04 -04:00
Rubén De la Torre Vico
8e4847ec89 fix(rds): add comprobations before list tags (#5249)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-30 13:34:22 -04:00
Sergio Garcia
c6d34e8089 chore(README): update summary table (#5248) 2024-09-30 12:56:42 -04:00
Hugo Pereira Brito
880523076d feat(networkfirewall): add new check networkfirewall_policy_rule_group_associated (#5225) 2024-09-30 12:04:32 -04:00
Sergio Garcia
3d2f1a3aa7 fix(aws): handle none type attributes (#5216) 2024-09-30 18:04:14 +02:00
Rubén De la Torre Vico
c9ff96144d chore(ssm): add tags to ssm_managed_compliant_patching (#5245)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-30 12:00:43 -04:00
johannes-engler-mw
234f8c2958 feat(azure containerregistry): gather service infos and checks disabled admin user (#5191)
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-30 11:52:48 -04:00
Sergio Garcia
da87c0d81e fix(tests): patch head_bucket function correctly (#5246) 2024-09-30 11:00:30 -04:00
dependabot[bot]
7732ec7d34 chore(deps-dev): bump safety from 3.2.7 to 3.2.8 (#5238)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 10:54:36 -04:00
Rubén De la Torre Vico
a1b9b2171f feat(securityhub): add tags securityhub_enabled (#5231)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-30 10:13:41 -04:00
Mario Rodriguez Lopez
30e3fd9e46 feat(ecs): Ensure ECS containers have a logging configuration specified (#5234)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-30 09:43:20 -04:00
dependabot[bot]
3db541a42a chore(deps): bump botocore from 1.35.28 to 1.35.29 (#5239)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 09:38:12 -04:00
Rubén De la Torre Vico
d5abe16180 feat(wafv2): add tags to wafv2_webacl_logging_enabled (#5243) 2024-09-30 09:37:16 -04:00
dependabot[bot]
564b18c388 chore(deps): bump azure-storage-blob from 12.23.0 to 12.23.1 (#5240)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 08:30:28 -04:00
Rubén De la Torre Vico
13e40eb03e feat(aws): add tags to Global Accelerator (#5233) 2024-09-27 12:37:19 -04:00
Rubén De la Torre Vico
b402ced402 docs: change installation methods (#5192) 2024-09-27 12:15:14 -04:00
dependabot[bot]
6bbb9d04a6 chore(deps): bump boto3 from 1.35.26 to 1.35.28 (#5232)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 12:13:56 -04:00
dependabot[bot]
6616657c91 chore(deps): bump botocore from 1.35.27 to 1.35.28 (#5220)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 11:30:21 -04:00
Amogh Bantwal
853b833cfb feat(aws): Add new check opensearch_service_domains_access_control_enabled (#5203)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-27 10:13:43 -04:00
Rubén De la Torre Vico
c047b29140 feat(rds): add missing tags to RDS checks (#5230) 2024-09-27 09:34:25 -04:00
Prowler Bot
c4a39662ae chore(regions_update): Changes in regions for AWS services (#5224)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-27 12:30:05 +02:00
dependabot[bot]
66e804f212 chore(deps): bump trufflesecurity/trufflehog from 3.82.5 to 3.82.6 (#5222)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 08:42:07 +02:00
Mario Rodriguez Lopez
9d4fa55c13 feat(ecs): Ensure ECS task definitions host's process namespace is not shared (#5146)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-26 18:24:21 -04:00
Mario Rodriguez Lopez
ff05ce4da1 feat(ecs): Ensure ECS containers have read-only access to root filesystems (#5168)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-26 14:37:24 -04:00
Mario Rodriguez Lopez
0474c7995c feat(ecs): Ensure ECS containers run as non-privileged (#5214) 2024-09-26 14:05:11 -04:00
Mario Rodriguez Lopez
1a679f371f feat(ecr): Ensure ECR repositories have tag immutability configured (#5144)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-26 13:51:57 -04:00
Rubén De la Torre Vico
05f7170add feat(dms): add tags to DMS checks (#5209) 2024-09-26 13:33:28 -04:00
Rubén De la Torre Vico
19acb873af feat(glue): add tags to Glue checks (#5213) 2024-09-26 13:11:44 -04:00
Daniel Barranquero
0b566f9666 feat(dynamodb): add new check dynamodb_table_deletion_protection_enabled (#5148) 2024-09-26 11:19:57 -04:00
Rubén De la Torre Vico
67bf89537a chore(ec2): add tags to report of EC2 launch templates (#5210)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-26 10:50:02 -04:00
Daniel Barranquero
d0681a9e20 fix(aws): change protected_by_backup_plan checks (#5204)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-26 10:33:12 -04:00
Rubén De la Torre Vico
31bff99b3d feat(codebuild): add tags support to projects (#5207) 2024-09-26 10:14:02 -04:00
Rubén De la Torre Vico
48c7e65a39 chore(autoscaling): deprecate check autoscaling_find_secrets_ec2_launch_configuration (#5205) 2024-09-26 10:11:54 -04:00
dependabot[bot]
1b407639f0 chore(deps): bump azure-mgmt-network from 26.0.0 to 27.0.0 (#5201)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 15:36:42 +02:00
Prowler Bot
4d7d5718d5 chore(regions_update): Changes in regions for AWS services (#5208)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-26 08:20:13 -04:00
dependabot[bot]
7955048e79 chore(deps-dev): bump mkdocs-material from 9.5.36 to 9.5.38 (#5206)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 10:51:29 +02:00
dependabot[bot]
8e0b715f12 chore(deps): bump trufflesecurity/trufflehog from 3.82.3 to 3.82.5 (#5202)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 10:07:17 +02:00
dependabot[bot]
1d81261d97 chore(deps): bump botocore from 1.35.26 to 1.35.27 (#5199)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 10:06:40 +02:00
Mario Rodriguez Lopez
114a3088a4 feat(ecs): Ensure public IP addresses are not assigned automatically (#5128)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-25 16:24:39 -04:00
Rubén De la Torre Vico
bc8f3eba4d feat(backup): add tags to backup vaults and backup plans (#5194) 2024-09-25 11:02:53 -04:00
Hugo Pereira Brito
8e087196c9 feat(s3): Add new check s3_bucket_cross_account_access (#5082)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-25 10:01:52 -04:00
Amogh Bantwal
744e7ff5ac feat(threat-detection): Use IAM Identity for Cloudtrail Threat Detection instead of IP (#5166) 2024-09-25 09:15:47 -04:00
Prowler Bot
90b84b57d3 chore(regions_update): Changes in regions for AWS services (#5190)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-25 09:07:42 -04:00
Sergio Garcia
0a2b7cf152 chore(aws): improve IAM Resource Policy public logic (#5067)
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
2024-09-25 08:33:41 -04:00
Pedro Martín
ebbccd04f1 refactor(execute_check): refactor execute method (#4975) 2024-09-25 14:19:42 +02:00
dependabot[bot]
2b431fc79f chore(deps-dev): bump pylint from 3.3.0 to 3.3.1 (#5187)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 12:19:02 +02:00
dependabot[bot]
fe7c3e7548 chore(deps): bump google-api-python-client from 2.146.0 to 2.147.0 (#5185)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 10:34:04 +02:00
dependabot[bot]
0e5f929044 chore(deps): bump boto3 from 1.35.24 to 1.35.26 (#5189)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 09:39:06 +02:00
Pedro Martín
47a6e28d71 refactor(output_options): remove output options from provider (#5149) 2024-09-25 09:38:21 +02:00
Jude Bae(Bae cheongho)
de5742433b feat(compliance): add KISA ISMS-P compliance framework (#5086)
Co-authored-by: MZC01-JUDE <mzc01-jude@MZC01-JUDE-2.local>
2024-09-25 09:06:05 +02:00
dependabot[bot]
3fcccd0bcd chore(deps): bump botocore from 1.35.25 to 1.35.26 (#5184)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 08:42:35 +02:00
dependabot[bot]
00938cadb1 chore(deps): bump trufflesecurity/trufflehog from 3.82.2 to 3.82.3 (#5183)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 08:42:06 +02:00
Daniel Barranquero
9fb26643ba feat(dynamodb): add new check dynamodb_accelerator_cluster_in_transit_encryption_enabled (#5173)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-24 16:32:37 -04:00
Daniel Barranquero
e4890f9d9d feat(dynamodb): add new check dynamodb_table_protected_by_backup_plan (#5175)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-24 12:45:12 -04:00
Hugo Pereira Brito
980b9b4770 feat(networkfirewall): change network_firewalls from list to dict (#5169) 2024-09-24 12:43:19 -04:00
Sergio Garcia
348cea67c0 fix(aws): always use audited partition (#5174) 2024-09-24 11:38:11 -04:00
Sergio Garcia
f4d89066d9 feat(aws): add new check organizations_opt_out_ai_services_policy (#5152) 2024-09-24 11:37:03 -04:00
dependabot[bot]
b26dc899be chore(deps-dev): bump moto from 5.0.14 to 5.0.15 (#5158)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 09:04:52 -04:00
Sergio Garcia
25327d618d chore(aws): handle NotAction cases in IAM policies (#5035) 2024-09-24 08:36:11 -04:00
Sergio Garcia
3951295c0c chore(organizations): improve AWS Organizations service (#5151) 2024-09-24 08:28:21 -04:00
Prowler Bot
ff9c3b52d6 chore(regions_update): Changes in regions for AWS services (#5167)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-24 08:17:19 -04:00
dependabot[bot]
af8c18eb4e chore(deps-dev): bump bandit from 1.7.9 to 1.7.10 (#5157)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 08:16:27 -04:00
dependabot[bot]
6fbfcc7f5f chore(deps): bump botocore from 1.35.24 to 1.35.25 (#5155)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 12:33:29 +02:00
dependabot[bot]
7c7132f9c4 chore(deps-dev): bump mkdocs-material from 9.5.35 to 9.5.36 (#5156)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 11:04:06 +02:00
dependabot[bot]
62e30f929c chore(deps): bump boto3 from 1.35.23 to 1.35.24 (#5154)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 10:06:27 +02:00
Pepe Fagoaga
ddaafd5876 chore(bot): Use bot Token (#5163) 2024-09-24 10:06:00 +02:00
Mario Rodriguez Lopez
1f43e6eff9 feat(inspector2): Add more tests to inspector2_is_enabled check (#5150) 2024-09-23 15:06:34 -04:00
Daniel Barranquero
aa118c05c5 feat(rds): add new check rds_cluster_non_default_port (#5113) 2024-09-23 15:05:56 -04:00
Hugo Pereira Brito
cca17b9378 feat(cloudfront): add new check cloudfront_distributions_s3_origin_non_existing_bucket (#4996)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-23 12:43:03 -04:00
Pedro Martín
14ed19e3a8 fix(iam): fill resource id with inline policy entity (#5120) 2024-09-23 10:54:38 -04:00
dependabot[bot]
8caf8f794c chore(deps): bump azure-mgmt-cosmosdb from 9.5.1 to 9.6.0 (#5111)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 09:38:15 -04:00
dependabot[bot]
cba9ad61e4 chore(deps): bump msgraph-sdk from 1.7.0 to 1.8.0 (#5110)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 08:48:42 -04:00
dependabot[bot]
e64a0eff0f chore(deps): bump botocore from 1.35.23 to 1.35.24 (#5140)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 08:01:40 -04:00
dependabot[bot]
23c65b8fde chore(deps): bump pandas from 2.2.2 to 2.2.3 (#5139)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 10:33:38 +02:00
dependabot[bot]
a7c93f3237 chore(deps-dev): bump pylint from 3.2.7 to 3.3.0 (#5138)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 09:41:04 +02:00
dependabot[bot]
7b9402f3d0 chore(deps): bump kubernetes from 30.1.0 to 31.0.0 (#5137)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-23 08:29:49 +02:00
Sergio Garcia
4badcca4f8 fix(gcp): add default project for org level checks (#5003) 2024-09-20 20:39:35 +02:00
Hugo Pereira Brito
c6daa60f26 feat(elasticache): add check elasticache_redis_cluster_auth_enabled (#4830)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-20 12:18:08 -04:00
Harshit Raj Singh
f9aa2bb8be fix(lightsail): Remove second call to is_resource_filtered (#5044) 2024-09-20 11:39:03 -04:00
Rubén De la Torre Vico
66ac395705 chore(README): update checks summary table (#5119) 2024-09-20 11:27:19 -04:00
Sergio Garcia
16a251254e fix(gcp): solve errors in GCP services (#5016) 2024-09-20 11:06:57 -04:00
Sergio Garcia
751958907c fix(vpc): check all routes tables in subnet (#5081) 2024-09-20 10:13:16 -04:00
Sergio Garcia
60012ab19d chore(deps): update docs dependencies (#5098) 2024-09-20 10:13:09 -04:00
dependabot[bot]
65d7ba020b chore(deps): bump boto3 from 1.35.21 to 1.35.23 (#5115)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 09:13:09 -04:00
Sergio Garcia
9456c6198a chore(ssm): add trusted accounts variable to ssm check (#5005)
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2024-09-20 09:12:48 -04:00
Sergio Garcia
45ce1a0650 fix(asff): include status extended in ASFF output (#5097) 2024-09-20 09:08:13 -04:00
dependabot[bot]
4c5db5295c chore(deps): bump botocore from 1.35.22 to 1.35.23 (#5109)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 14:18:55 +02:00
dependabot[bot]
a2ad0cdf30 chore(deps): bump azure-identity from 1.17.1 to 1.18.0 (#5108)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 13:29:43 +02:00
dependabot[bot]
0c70a64e84 chore(deps): bump slack-sdk from 3.33.0 to 3.33.1 (#5107)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-20 11:57:41 +02:00
Mario Rodriguez Lopez
73c96f8346 feat(sagemaker): Ensure SageMaker Endpoint Production Variants have Initial Instance Count greater than one (#5045)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-19 15:16:56 -04:00
Amogh Bantwal
0974c5f333 feat(slack): add more information about critical findings (#5042) 2024-09-19 14:02:09 -04:00
Hugo Pereira Brito
7db0746416 feat(guardduty): add new check guardduty_rds_protection_enabled (#5100) 2024-09-19 13:52:17 -04:00
dependabot[bot]
8f0bf5e896 chore(deps-dev): bump pytest-env from 1.1.4 to 1.1.5 (#5090)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-19 12:29:43 -04:00
Pedro Martín
57abe1c839 fix(accessanalyzer): refactor accessanalyzer enabled fixer test (#5026) 2024-09-19 11:09:03 -04:00
Daniel Barranquero
43183962ad feat(aws): Add new check to ensure RDS instances are not using default database engine ports (#4973)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-19 10:14:46 -04:00
Daniel Barranquero
87948b458e feat(guardduty): add new check guardduty_s3_protection_enabled (#5087)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-19 10:10:39 -04:00
dependabot[bot]
ab5c3eb4f8 chore(deps): bump botocore from 1.35.21 to 1.35.22 (#5089)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-19 09:30:24 -04:00
Rubén De la Torre Vico
320a2a2c77 feat(awslambda): add new check awslambda_function_vpc_multi_az (#4816)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-19 09:30:04 -04:00
Sergio Garcia
dbc8e140e3 chore(docs): change ResourceType link of Security Hub (#5063) 2024-09-19 07:25:41 -04:00
Hugo Pereira Brito
21ac395d4c fix(elasticache): get correct automatic failover attribute (#5084)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-18 18:29:43 -04:00
Mario Rodriguez Lopez
8a8c2b5097 feat(ecs): add new check ecs_task_definitions_host_networking_mode_users (#5088)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-18 18:28:31 -04:00
dependabot[bot]
3bea772c6b chore(deps): bump slack-sdk from 3.32.0 to 3.33.0 (#5069)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 18:28:20 -04:00
Lefteris
34679c98d6 feat(dms): new check dms_endpoint_ssl_enabled (#4968)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
2024-09-18 17:46:56 -04:00
dependabot[bot]
2b41445d57 chore(deps): bump boto3 from 1.35.19 to 1.35.21 (#5085)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 17:21:02 -04:00
dependabot[bot]
796c87bc93 chore(deps): bump google-api-python-client from 2.145.0 to 2.146.0 (#5070)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 16:32:09 -04:00
dependabot[bot]
a83e08aa9e chore(deps-dev): bump vulture from 2.11 to 2.12 (#5071)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 13:59:25 -04:00
Hugo Pereira Brito
ae794c7c32 feat(cloudfront): Ensure Cloudfront distributions have origin failover configured (#4868)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-18 13:26:35 -04:00
dependabot[bot]
edc78bfd6b chore(deps): bump botocore from 1.35.20 to 1.35.21 (#5073)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 13:18:17 -04:00
dependabot[bot]
9263adeb78 chore(deps): bump azure-storage-blob from 12.22.0 to 12.23.0 (#5072)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 12:19:46 -04:00
Prowler Bot
bfdc87723b chore(regions_update): Changes in regions for AWS services (#5080)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-18 11:33:01 -04:00
Rubén De la Torre Vico
8d23e81b1c feat(elb): add new check elb_connection_draining_enabled (#5014)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-18 10:49:33 -04:00
Daniel Barranquero
f0cd924016 feat(neptune): add new check neptune_cluster_copy_tags_to_snapshots (#5062)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-18 09:23:44 -04:00
Mario Rodriguez Lopez
c425e8249b fix(inspector2): Ensure Inspector2 is enabled for ECR, EC2, Lambda and Lambda Code (#5061)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-17 14:01:19 -04:00
Daniel Barranquero
1ece8bbcd6 feat(neptune): add new check neptune_cluster_snapshot_encrypted (#5058)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-17 13:16:43 -04:00
Daniel Barranquero
5fb2d7c3ce feat(neptune): add new check neptune_cluster_integration_cloudwatch_logs (#5048)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-17 12:20:25 -04:00
Prowler Bot
64aebe84fe chore(regions_update): Changes in regions for AWS services (#5059)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-17 11:52:41 -04:00
Rubén De la Torre Vico
de831b0abe chore(AWS): match all AWS resource types with SecurityHub supported types in metadata (#4882)
Co-authored-by: Sergio <sergio@prowler.com>
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-17 11:40:45 -04:00
dependabot[bot]
68af4f6c73 chore(deps): bump botocore from 1.35.19 to 1.35.20 (#5053)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 11:39:24 -04:00
dependabot[bot]
52981b54b9 chore(deps): bump trufflesecurity/trufflehog from 3.82.1 to 3.82.2 (#5052)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 08:36:42 -04:00
dependabot[bot]
a366594714 chore(deps): bump boto3 from 1.35.16 to 1.35.19 (#5049)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 15:16:44 -04:00
Hugo Pereira Brito
1fb36f316b fix(cloudfront): duplicated link in cloudfront_distributions_https_sni_enabled check (#5047) 2024-09-16 15:16:26 -04:00
dependabot[bot]
30ffa8f00b chore(deps): bump azure-mgmt-containerservice from 31.0.0 to 32.0.0 (#5036)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 13:45:01 -04:00
Prowler Bot
5855918ade chore(regions_update): Changes in regions for AWS services (#5041)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-16 13:44:47 -04:00
dependabot[bot]
f9005c875f chore(deps): bump botocore from 1.35.18 to 1.35.19 (#5037)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 12:52:59 -04:00
Mario Rodriguez Lopez
91bf99ca45 feat(ec2): Ensure EC2 launch templates do not assign public IPs (#4852)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-16 12:52:40 -04:00
dependabot[bot]
8176063fef chore(deps): bump dash from 2.18.0 to 2.18.1 (#5024)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-16 11:25:08 -04:00
Mario Rodriguez Lopez
3373822240 feat(ec2): EBS Volumes Should Be Covered by a Backup Plan (#5028)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-16 11:23:23 -04:00
Hugo Pereira Brito
7e16702b2f feat(cloudfront): add cloudfront_distributions_origin_traffic_encrypted check to ensure traffic encryption to custom origins (#4958)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-16 09:12:37 -04:00
Daniel Barranquero
f54b64f1f8 feat(rds): add new check rds_instance_inside_vpc (#5029)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-16 08:56:39 -04:00
dependabot[bot]
2c337ab3f6 chore(deps-dev): bump mkdocs-git-revision-date-localized-plugin from 1.2.8 to 1.2.9 (#5023)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 14:44:33 -04:00
dependabot[bot]
5279d937d7 chore(deps): bump botocore from 1.35.17 to 1.35.18 (#5021)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 11:24:55 -04:00
Hugo Pereira Brito
48c31a1616 feat(cloudfront): Add new cloudfront_distributions_s3_origin_access_control check to ensure OAC is configured in distributions (#4939)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-13 10:51:49 -04:00
Rubén De la Torre Vico
917a2ad0fe docs(check): change where extract ResourceTypes (#5030) 2024-09-13 10:51:09 -04:00
Rubén De la Torre Vico
8cfc4c56cf docs(dev-guide): refer poetry docs for installation (#5031) 2024-09-13 10:45:57 -04:00
Prowler Bot
99e9e42a17 chore(regions_update): Changes in regions for AWS services (#5027)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-13 10:38:08 -04:00
dependabot[bot]
13c95ba131 chore(deps): bump trufflesecurity/trufflehog from 3.81.10 to 3.82.1 (#5025)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-13 08:59:06 -04:00
LefterisXefteris
600a8c7804 chore(aws): add mixed regions test for s3_access_point_public_access_block (#4877)
Co-authored-by: Lefteris Gilmaz <lefterisgilmaz@Lefteriss-MacBook-Pro.local>
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-12 15:58:39 -04:00
Hugo Pereira Brito
64fb52fc5e feat(cloudfront): add new check cloudfront_distributions_custom_ssl_certificate (#4959)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-12 15:00:48 -04:00
Mario Rodriguez Lopez
92b6e7230d feat(ec2): Amazon EC2 Instances Should Not Use Multiple ENIs (#4935)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-12 14:29:36 -04:00
Hugo Pereira Brito
cc8bc781c1 feat(elasticache): Ensure Redis replication groups have automatic failover enabled (#4853)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-12 12:23:15 -04:00
Hugo Pereira Brito
edbe463d73 feat(cloudfront): Add new check cloudfront_distributions_default_root_object (#4938)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-12 10:58:24 -04:00
Sergio Garcia
8ace8c01cf chore(refactor): make Provider generation global (#4961)
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2024-09-12 16:56:58 +02:00
Hugo Pereira Brito
8f37252676 feat(cloudfront): Ensure distributions use SNI to serve HTTPS requests (#4888)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-12 09:28:26 -04:00
Mario Rodriguez Lopez
c0c59968bf feat(ec2): Ensure both VPN tunnels for an AWS Site-to-Site VPN connection are UP (#4948) 2024-09-12 08:26:35 -04:00
dependabot[bot]
9f5a909be3 chore(deps): bump msgraph-sdk from 1.6.0 to 1.7.0 (#5013)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-12 14:10:50 +02:00
dependabot[bot]
90975bdadc chore(deps): bump pytz from 2024.1 to 2024.2 (#5012)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-12 11:32:58 +02:00
dependabot[bot]
7d1fad9eb7 chore(deps): bump botocore from 1.35.16 to 1.35.17 (#5011)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-12 10:50:07 +02:00
dependabot[bot]
983c79ad3b chore(deps): bump boto3 from 1.35.15 to 1.35.16 (#5010)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-12 09:16:53 +02:00
Mario Rodriguez Lopez
96e73fcb63 feat(ec2): Amazon EC2 Paravirtual Instance Types Should Not Be Used (#4922)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-11 15:56:20 -04:00
Pedro Martín
70a3736073 fix(awslamba): add audit config to lambda_client in tests (#4999) 2024-09-11 12:15:22 -04:00
Pedro Martín
1e8e8ba65c fix(iam-gcp): add getters in iam_service for gcp (#4998) 2024-09-11 11:01:58 -04:00
dependabot[bot]
359a1f2c8e chore(deps): bump botocore from 1.35.15 to 1.35.16 (#4989)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-11 10:53:18 -04:00
Mario Rodriguez Lopez
2e4f8cbfc7 feat(ec2): Ensure not default Network Access Control Lists are used (#4917)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-11 09:55:18 -04:00
Prowler Bot
482aee0d9d chore(regions_update): Changes in regions for AWS services (#4995)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-11 09:52:28 -04:00
Daniel Barranquero
0ae3374e81 feat(aws): Add new check to ensure Aurora MySQL DB Clusters publish audit logs to CloudWatch logs (#4916)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-11 09:10:49 -04:00
Mario Rodriguez Lopez
ddc088859e feat(vpc): Ensure Amazon EC2 Is Configured to Use VPC Endpoints Created for the Amazon EC2 Service (#4872) 2024-09-11 09:08:25 -04:00
dependabot[bot]
5e3da2d687 chore(deps): bump google-api-python-client from 2.144.0 to 2.145.0 (#4990)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 13:28:13 +02:00
Pedro Martín
1af7f658a8 refactor(azure): remove validate_arguments for CLI (#4985) 2024-09-11 13:13:06 +02:00
dependabot[bot]
1298620da8 chore(deps-dev): bump pytest from 8.3.2 to 8.3.3 (#4991)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-11 08:59:46 +02:00
Hugo Pereira Brito
75c48cfaa3 refactor(cloudfront): replace origins dictionary with custom Origin class (#4981)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-10 16:04:57 -04:00
Sergio Garcia
3406a07ae5 fix(audit): solve resources audit (#4983) 2024-09-10 15:41:59 -04:00
Sergio Garcia
cc9e1c5af8 chore(dependencies): update boto3 and botocore packages (#4976) 2024-09-10 15:36:23 -04:00
Sergio Garcia
0343f01cca chore(README): update summary table (#4984) 2024-09-10 21:17:33 +02:00
dependabot[bot]
cad7985c28 chore(deps-dev): bump moto from 5.0.13 to 5.0.14 (#4965)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-10 14:36:21 -04:00
Pedro Martín
71030f6f42 fix(main): logic for resource_tag and resource_arn usage (#4979)
Co-authored-by: Sergio <sergio@prowler.com>
2024-09-10 14:07:07 -04:00
Daniel Barranquero
6883467d2f feat(aws): Add new check to ensure RDS DB clusters are encrypted at rest (#4931) 2024-09-10 13:40:08 -04:00
Sergio Garcia
2c6944176f fix(rds): handle new rds arn template function syntax (#4980) 2024-09-10 13:24:19 -04:00
Daniel Barranquero
1ef15f0b24 feat(aws): Add new check to ensure RDS event notification subscriptions are configured for critical database parameter group events (#4907) 2024-09-10 11:10:57 -04:00
dependabot[bot]
f5b0583df5 chore(deps-dev): bump pytest-env from 1.1.3 to 1.1.4 (#4966)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 10:17:36 -04:00
Daniel Barranquero
db225e9d2a feat(aws): Add new RDS check to ensure db instances are protected by a backup plan (#4879)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-09-10 10:14:40 -04:00
Daniel Barranquero
c9ae9df87f feat(aws): Add new check to ensure RDS event notification subscriptions are configured for critical database instance events (#4891) 2024-09-10 09:26:15 -04:00
Daniel Barranquero
159a090c02 feat(aws): Add new check to ensure RDS event notification subscriptions are configured for critical cluster events (#4887) 2024-09-10 09:25:42 -04:00
Daniel Barranquero
605c6770e5 fix(rds): Modify RDS Event Notification Subscriptions for Security Groups Events check (#4969) 2024-09-10 09:13:46 -04:00
Pedro Martín
ae950484ed fix(aws): make intersection to retrieve checks to execute (#4970) 2024-09-10 13:24:35 +02:00
Prowler Bot
c54b815b90 chore(regions_update): Changes in regions for AWS services (#4971)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-10 12:55:06 +02:00
Pedro Martín
7a937c7708 refactor(provider): move audit and fixer config inside the provider (#4960) 2024-09-10 09:48:11 +02:00
dependabot[bot]
d62e74853e chore(deps-dev): bump mkdocs-git-revision-date-localized-plugin from 1.2.7 to 1.2.8 (#4967)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-10 09:22:10 +02:00
Mario Rodriguez Lopez
bab59bc86e feat(EC2): Change service to adjust the data saved in template_data in LaunchTemplateVersion (#4848) 2024-09-09 12:32:39 -04:00
dependabot[bot]
39e8485fc1 chore(deps): bump slack-sdk from 3.31.0 to 3.32.0 (#4955)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-09 11:10:40 +02:00
Prowler Bot
b9f46cafff chore(regions_update): Changes in regions for AWS services (#4956)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-09-09 09:15:40 +02:00
Pedro Martín
48377ca865 feat(azure): add custom exception class (#4871) 2024-09-06 14:50:27 +02:00
Pedro Martín
4d902e02bb fix(security-groups): remove RFC1918 from ec2_securitygroup_allow_wide_open_public_ipv4 (#4951) 2024-09-06 13:42:28 +02:00
Pedro Martín
e146491d4b fix(aws): change check metadata ec2_securitygroup_allow_wide_open_public_ipv4 (#4946) 2024-09-06 12:31:19 +02:00
Pedro Martín
4eed5c7a99 refactor(check_metadata): move bulk_load_checks_metadata inside class (#4934) 2024-09-06 09:50:14 +02:00
dependabot[bot]
f169599a56 chore(deps): bump msgraph-sdk from 1.5.4 to 1.6.0 (#4940)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2024-09-06 09:49:20 +02:00
819 changed files with 41472 additions and 8211 deletions

View File

@@ -153,7 +153,7 @@ jobs:
run: |
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
-H "Authorization: Bearer ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data '{"event_type":"dispatch","client_payload":{"version":"v3-latest", "tag": "${{ env.LATEST_COMMIT_HASH }}"}}'
@@ -162,6 +162,6 @@ jobs:
run: |
curl https://api.github.com/repos/${{ secrets.DISPATCH_OWNER }}/${{ secrets.DISPATCH_REPO }}/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.ACCESS_TOKEN }}" \
-H "Authorization: Bearer ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data '{"event_type":"dispatch","client_payload":{"version":"release", "tag":"${{ needs.container-build-push.outputs.prowler_version }}"}}'

View File

@@ -11,7 +11,7 @@ jobs:
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@v3.81.10
uses: trufflesecurity/trufflehog@v3.82.7
with:
path: ./
base: ${{ github.event.repository.default_branch }}

View File

@@ -2,9 +2,9 @@ FROM python:3.12-alpine
LABEL maintainer="https://github.com/prowler-cloud/prowler"
# Update system dependencies
# Update system dependencies and install essential tools
#hadolint ignore=DL3018
RUN apk --no-cache upgrade && apk --no-cache add curl
RUN apk --no-cache upgrade && apk --no-cache add curl git
# Create nonroot user
RUN mkdir -p /home/prowler && \
@@ -13,18 +13,17 @@ RUN mkdir -p /home/prowler && \
chown -R prowler:prowler /home/prowler
USER prowler
# Copy necessary files
# Copy necessary files
WORKDIR /home/prowler
COPY prowler/ /home/prowler/prowler/
COPY dashboard/ /home/prowler/dashboard/
COPY pyproject.toml /home/prowler
COPY README.md /home/prowler
# Install dependencies
# Install Python dependencies
ENV HOME='/home/prowler'
ENV PATH="$HOME/.local/bin:$PATH"
#hadolint ignore=DL3013
RUN pip install --no-cache-dir --upgrade pip && \
RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
pip install --no-cache-dir .
# Remove deprecated dash dependencies

View File

@@ -63,9 +63,9 @@ 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 | 409 | 67 -> `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 | 135 | 16 -> `prowler azure --list-services` | 2 -> `prowler azure --list-compliance` | 2 -> `prowler azure --list-categories` |
| AWS | 457 | 67 -> `prowler aws --list-services` | 30 -> `prowler aws --list-compliance` | 9 -> `prowler aws --list-categories` |
| GCP | 77 | 13 -> `prowler gcp --list-services` | 2 -> `prowler gcp --list-compliance` | 2 -> `prowler gcp --list-categories`|
| Azure | 136 | 17 -> `prowler azure --list-services` | 3 -> `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` |
# 💻 Installation

View File

@@ -12,7 +12,11 @@ Originally based on [org-multi-account](https://github.com/prowler-cloud/prowler
## Architecture Explanation
The solution is designed to be very simple. Prowler is run via an ECS Task definition that launches a single Fargate container. This Task Definition is executed on a schedule using an EventBridge Rule.
The solution is designed to be very simple. Prowler is run via an ECS Task definition that launches a single Fargate container. This Task Definition is executed on a schedule using an EventBridge Rule.
## Prerequisites
This solution assumes that you have a VPC architecture with two redundant subnets that can reach the AWS API endpoints (e.g. PrivateLink, NAT Gateway, etc.).
## CloudFormation Templates
@@ -59,9 +63,9 @@ The logs that are generated and sent to Cloudwatch are error logs, and assessmen
## Instructions
1. Create a Private ECR Repository in the account that will host the Prowler container. The Audit account is recommended, but any account can be used.
2. Configure the .awsvariables file. Note the ROLE name chosen as it will be the CrossAccountRole.
3. Follow the steps from "View Push Commands" to build and upload the container image. You need to have Docker and AWS CLI installed, and use the cli to login to the account first. After upload note the Image URI, as it is required for the CF-Prowler-ECS template.
4. Make sure SecurityHub is enabled in every account in AWS Organizations, and that the SecurityHub integration is enabled as explained in [Prowler - Security Hub Integration](https://github.com/prowler-cloud/prowler#security-hub-integration)
2. Configure the .awsvariables file. Note the ROLE name chosen as it will be the CrossAccountRole.
3. Follow the steps from "View Push Commands" to build and upload the container image. Substitute step 2 with the build command provided in the Dockerfile. You need to have Docker and AWS CLI installed, and use the cli to login to the account first. After upload note the Image URI, as it is required for the CF-Prowler-ECS template. Ensure that you pay attention to the architecture while performing the docker build command. A common mistake is not specifying the architecture and then building on Apple silicon. Your task will fail with *exec /home/prowler/.local/bin/prowler: exec format error*.
4. Make sure SecurityHub is enabled in every account in AWS Organizations, and that the SecurityHub integration is enabled as explained in [Prowler - Security Hub Integration](https://github.com/prowler-cloud/prowler#security-hub-integration)
5. Deploy **CF-Prowler-CrossAccountRole.yml** in the Master Account as a single stack. You will have to choose the CrossAccountRole name (ProwlerXA-Role by default) and the ProwlerTaskRoleName (ProwlerECSTask-Role by default)
6. Deploy **CF-Prowler-CrossAccountRole.yml** in every Member Account as a StackSet. Choose the same CrossAccountName and ProwlerTaskRoleName as the previous step.
7. Deploy **CF-Prowler-IAM.yml** in the account that will host the Prowler container (the same from step 1). The following template parameters must be provided:
@@ -91,4 +95,4 @@ If you permission find errors in the CloudWatch logs, the culprit might be a [Se
## Upgrading Prowler
Prowler version is controlled by the PROWLERVER argument in the Dockerfile, change it to the desired version and follow the ECR Push Commands to update the container image.
Old images can be deleted from the ECR Repository after the new image is confirmed to work. They will show as "untagged" as only one image can hold the "latest" tag.
Old images can be deleted from the ECR Repository after the new image is confirmed to work. They will show as "untagged" as only one image can hold the "latest" tag.

View File

@@ -68,7 +68,7 @@ for accountId in ${ACCOUNTS_IN_ORGS}; do
# Run Prowler
echo -e "Assessing AWS Account: ${accountId}, using Role: ${ROLE} on $(date)"
# Pipe stdout to /dev/null to reduce unnecessary Cloudwatch logs
prowler aws -R arn:"${PARTITION}":iam::"${accountId}":role/"${ROLE}" -q -S -f "${REGION}" > /dev/null
prowler aws -R arn:"${PARTITION}":iam::"${accountId}":role/"${ROLE}" --security-hub --send-sh-only-fails -f "${REGION}" > /dev/null
TOTAL_SEC=$((SECONDS - START_TIME))
printf "Completed AWS Account: ${accountId} in %02dh:%02dm:%02ds" $((TOTAL_SEC / 3600)) $((TOTAL_SEC % 3600 / 60)) $((TOTAL_SEC % 60))
echo ""

View File

@@ -60,24 +60,42 @@ Resources:
Effect: Allow
Resource: "*"
Action:
- ds:ListAuthorizedApplications
- account:Get*
- appstream:Describe*
- appstream:List*
- backup:List*
- cloudtrail:GetInsightSelectors
- codeartifact:List*
- codebuild:BatchGet*
- cognito-idp:GetUserPoolMfaConfig
- dlm:Get*
- drs:Describe*
- ds:Describe*
- ds:Get*
- ds:List*
- dynamodb:GetResourcePolicy
- ec2:GetEbsEncryptionByDefault
- ec2:GetSnapshotBlockPublicAccessState
- ec2:GetInstanceMetadataDefaults
- ecr:Describe*
- ecr:GetRegistryScanningConfiguration
- elasticfilesystem:DescribeBackupPolicy
- glue:GetConnections
- glue:GetSecurityConfiguration
- glue:GetSecurityConfiguration*
- glue:SearchTables
- lambda:GetFunction
- lambda:GetFunction*
- logs:FilterLogEvents
- lightsail:GetRelationalDatabases
- macie2:GetMacieSession
- s3:GetAccountPublicAccessBlock
- shield:DescribeProtection
- shield:GetSubscriptionState
- ssm:GetDocument
- ssm-incidents:List*
- support:Describe*
- tag:GetTagKeys
- PolicyName: Prowler-Security-Hub
PolicyDocument:
Version: 2012-10-17
Statement:
- wellarchitected:List*
- Sid: AllowProwlerSecurityHub
Effect: Allow
Resource: "*"

View File

@@ -62,7 +62,7 @@ Resources:
awslogs-stream-prefix: ecs
Cpu: 1024
ExecutionRoleArn: !Ref ECSExecutionRole
Memory: 2048
Memory: 8192
NetworkMode: awsvpc
TaskRoleArn: !Ref ProwlerTaskRole
Family: SecurityHubProwlerTask

View File

@@ -97,9 +97,15 @@ Outputs:
ECSExecutionRoleARN:
Description: ARN of the ECS Task Execution Role
Value: !GetAtt ECSExecutionRole.Arn
Export:
Name: ECSExecutionRoleArn
ProwlerTaskRoleARN:
Description: ARN of the ECS Prowler Task Role
Value: !GetAtt ProwlerTaskRole.Arn
Export:
Name: ProwlerTaskRoleArn
ECSEventRoleARN:
Description: ARN of the Eventbridge Task Role
Value: !GetAtt ECSEventRole.Arn
Export:
Name: ECSEventRoleARN

View File

@@ -2223,3 +2223,232 @@ def get_section_containers_ens(data, section_1, section_2, section_3, section_4)
section_containers.append(section_container)
return html.Div(section_containers, className="compliance-data-layout")
# This function extracts and compares up to two numeric values, ensuring correct sorting for version-like strings.
def extract_numeric_values(value):
numbers = re.findall(r"\d+", str(value))
if len(numbers) >= 2:
return int(numbers[0]), int(numbers[1])
elif len(numbers) == 1:
return int(numbers[0]), 0
return 0, 0
def get_section_containers_kisa_ismsp(data, section_1, section_2):
data["STATUS"] = data["STATUS"].apply(map_status_to_icon)
data[section_1] = data[section_1].astype(str)
data[section_2] = data[section_2].astype(str)
data.sort_values(
by=section_1,
key=lambda x: x.map(extract_numeric_values),
ascending=True,
inplace=True,
)
findings_counts_section = (
data.groupby([section_2, "STATUS"]).size().unstack(fill_value=0)
)
findings_counts_name = (
data.groupby([section_1, "STATUS"]).size().unstack(fill_value=0)
)
section_containers = []
for name in data[section_1].unique():
success_name = (
findings_counts_name.loc[name, pass_emoji]
if pass_emoji in findings_counts_name.columns
else 0
)
failed_name = (
findings_counts_name.loc[name, fail_emoji]
if fail_emoji in findings_counts_name.columns
else 0
)
fig_name = go.Figure(
data=[
go.Bar(
name="Failed",
x=[failed_name],
y=[""],
orientation="h",
marker=dict(color="#e77676"),
width=[0.8],
),
go.Bar(
name="Success",
x=[success_name],
y=[""],
orientation="h",
marker=dict(color="#45cc6e"),
width=[0.8],
),
]
)
fig_name.update_layout(
barmode="stack",
margin=dict(l=10, r=10, t=10, b=10),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
showlegend=False,
width=350,
height=30,
xaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
yaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
annotations=[
dict(
x=success_name + failed_name,
y=0,
xref="x",
yref="y",
text=str(success_name),
showarrow=False,
font=dict(color="#45cc6e", size=14),
xanchor="left",
yanchor="middle",
),
dict(
x=0,
y=0,
xref="x",
yref="y",
text=str(failed_name),
showarrow=False,
font=dict(color="#e77676", size=14),
xanchor="right",
yanchor="middle",
),
],
)
graph_name = dcc.Graph(
figure=fig_name, config={"staticPlot": True}, className="info-bar"
)
graph_div = html.Div(graph_name, className="graph-section")
direct_internal_items = []
for section in data[data[section_1] == name][section_2].unique():
specific_data = data[
(data[section_1] == name) & (data[section_2] == section)
]
success_section = (
findings_counts_section.loc[section, pass_emoji]
if pass_emoji in findings_counts_section.columns
else 0
)
failed_section = (
findings_counts_section.loc[section, fail_emoji]
if fail_emoji in findings_counts_section.columns
else 0
)
data_table = dash_table.DataTable(
data=specific_data.to_dict("records"),
columns=[
{"name": i, "id": i}
for i in ["CHECKID", "STATUS", "REGION", "ACCOUNTID", "RESOURCEID"]
],
style_table={"overflowX": "auto"},
style_as_list_view=True,
style_cell={"textAlign": "left", "padding": "5px"},
)
fig_section = go.Figure(
data=[
go.Bar(
name="Failed",
x=[failed_section],
y=[""],
orientation="h",
marker=dict(color="#e77676"),
),
go.Bar(
name="Success",
x=[success_section],
y=[""],
orientation="h",
marker=dict(color="#45cc6e"),
),
]
)
fig_section.update_layout(
barmode="stack",
margin=dict(l=10, r=10, t=10, b=10),
paper_bgcolor="rgba(0,0,0,0)",
plot_bgcolor="rgba(0,0,0,0)",
showlegend=False,
width=350,
height=30,
xaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
yaxis=dict(showticklabels=False, showgrid=False, zeroline=False),
annotations=[
dict(
x=success_section + failed_section,
y=0,
xref="x",
yref="y",
text=str(success_section),
showarrow=False,
font=dict(color="#45cc6e", size=14),
xanchor="left",
yanchor="middle",
),
dict(
x=0,
y=0,
xref="x",
yref="y",
text=str(failed_section),
showarrow=False,
font=dict(color="#e77676", size=14),
xanchor="right",
yanchor="middle",
),
],
)
graph_section = dcc.Graph(
figure=fig_section,
config={"staticPlot": True},
className="info-bar-child",
)
graph_div_section = html.Div(graph_section, className="graph-section-req")
internal_accordion_item = dbc.AccordionItem(
title=section,
children=[html.Div([data_table], className="inner-accordion-content")],
)
internal_section_container = html.Div(
[
graph_div_section,
dbc.Accordion(
[internal_accordion_item], start_collapsed=True, flush=True
),
],
className="accordion-inner--child",
)
direct_internal_items.append(internal_section_container)
accordion_item = dbc.AccordionItem(
title=f"{name}", children=direct_internal_items
)
section_container = html.Div(
[
graph_div,
dbc.Accordion([accordion_item], start_collapsed=True, flush=True),
],
className="accordion-inner",
)
section_containers.append(section_container)
return html.Div(section_containers, className="compliance-data-layout")

View File

@@ -0,0 +1,25 @@
import warnings
from dashboard.common_methods import get_section_containers_kisa_ismsp
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SUBDOMAIN",
"REQUIREMENTS_ATTRIBUTES_SECTION",
# "REQUIREMENTS_DESCRIPTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_kisa_ismsp(
aux, "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION"
)

View File

@@ -0,0 +1,25 @@
import warnings
from dashboard.common_methods import get_section_containers_kisa_ismsp
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_ATTRIBUTES_SUBDOMAIN",
"REQUIREMENTS_ATTRIBUTES_SECTION",
# "REQUIREMENTS_DESCRIPTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_kisa_ismsp(
aux, "REQUIREMENTS_ATTRIBUTES_SUBDOMAIN", "REQUIREMENTS_ATTRIBUTES_SECTION"
)

View File

@@ -272,7 +272,7 @@ Each Prowler check has metadata associated which is stored at the same level of
# Severity holds the check's severity, always in lowercase (critical, high, medium, low or informational)
"Severity": "critical",
# ResourceType only for AWS, holds the type from here
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
# https://docs.aws.amazon.com/securityhub/latest/userguide/asff-resources.html
"ResourceType": "Other",
# Description holds the title of the check, for now is the same as CheckTitle
"Description": "Ensure there are no EC2 AMIs set as Public.",

View File

@@ -14,10 +14,8 @@ Once that is satisfied go ahead and clone your forked repo:
git clone https://github.com/<your-github-user>/prowler
cd prowler
```
For isolation and avoid conflicts with other environments, we recommend usage of `poetry`:
```
pip install poetry
```
For isolation and to avoid conflicts with other environments, we recommend using `poetry`, a Python dependency management tool. You can install it by following the instructions [here](https://python-poetry.org/docs/#installation).
Then install all dependencies including the ones for developers:
```
poetry install --with dev

View File

@@ -19,14 +19,40 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
## Quick Start
### Installation
Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/), thus can be installed using pip with `Python >= 3.9`:
Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/), thus can be installed as Python package with `Python >= 3.9`:
=== "Generic"
=== "pipx"
[pipx](https://pipx.pypa.io/stable/) is a tool to install Python applications in isolated environments. It is recommended to use `pipx` for a global installation.
_Requirements_:
* `Python >= 3.9`
* `Python pip >= 3.9`
* `pipx` installed: [pipx installation](https://pipx.pypa.io/stable/installation/).
* AWS, GCP, Azure and/or Kubernetes credentials
_Commands_:
``` bash
pipx install prowler
prowler -v
```
To upgrade Prowler to the latest version, run:
``` bash
pipx upgrade prowler
```
=== "pip"
???+ warning
This method is not recommended because it will modify the environment which you choose to install. Consider using [pipx](https://docs.prowler.com/projects/prowler-open-source/en/latest/#__tabbed_1_1) for a global installation.
_Requirements_:
* `Python >= 3.9`
* `Python pip >= 21.0.0`
* AWS, GCP, Azure and/or Kubernetes credentials
_Commands_:
@@ -36,13 +62,19 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
prowler -v
```
To upgrade Prowler to the latest version, run:
``` bash
pip install --upgrade prowler
```
=== "Docker"
_Requirements_:
* Have `docker` installed: https://docs.docker.com/get-docker/.
* AWS, GCP, Azure and/or Kubernetes credentials
* In the command below, change `-v` to your local directory path in order to access the reports.
* AWS, GCP, Azure and/or Kubernetes credentials
_Commands_:
@@ -54,41 +86,21 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
--env AWS_SESSION_TOKEN toniblyx/prowler:latest
```
=== "Ubuntu"
_Requirements for Ubuntu 20.04.3 LTS_:
* AWS, GCP, Azure and/or Kubernetes credentials
* Install python 3.9 with: `sudo apt-get install python3.9`
* Remove python 3.8 to avoid conflicts if you can: `sudo apt-get remove python3.8`
* Make sure you have the python3 distutils package installed: `sudo apt-get install python3-distutils`
* To make sure you use pip for 3.9 get the get-pip script with: `curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py`
* Execute it with the proper python version: `sudo python3.9 get-pip.py`
* Now you should have pip for 3.9 ready: `pip3.9 --version`
_Commands_:
```
pip3.9 install prowler
export PATH=$PATH:/home/$HOME/.local/bin/
prowler -v
```
=== "GitHub"
_Requirements for Developers_:
* `git`
* `poetry` installed: [poetry installation](https://python-poetry.org/docs/#installation).
* AWS, GCP, Azure and/or Kubernetes credentials
* `git`, `Python >= 3.9`, `pip` and `poetry` installed (`pip install poetry`)
_Commands_:
```
git clone https://github.com/prowler-cloud/prowler
cd prowler
poetry shell
poetry install
python prowler.py -v
poetry run python prowler.py -v
```
???+ note
If you want to clone Prowler from Windows, use `git config core.longpaths true` to allow long file paths.
@@ -97,15 +109,33 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
_Requirements_:
* `Python >= 3.9`
* AWS, GCP, Azure and/or Kubernetes credentials
* Latest Amazon Linux 2 should come with Python 3.9 already installed however it may need pip. Install Python pip 3.9 with: `sudo yum install -y python3-pip`.
* Make sure setuptools for python is already installed with: `pip3 install setuptools`
_Commands_:
```
pip3.9 install prowler
export PATH=$PATH:/home/$HOME/.local/bin/
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install prowler
prowler -v
```
=== "Ubuntu"
_Requirements_:
* `Ubuntu 23.04` or above, if you are using an older version of Ubuntu check [pipx installation](https://docs.prowler.com/projects/prowler-open-source/en/latest/#__tabbed_1_1) and ensure you have `Python >= 3.9`.
* `Python >= 3.9`
* AWS, GCP, Azure and/or Kubernetes credentials
_Commands_:
``` bash
sudo apt update
sudo apt install pipx
pipx ensurepath
pipx install prowler
prowler -v
```
@@ -125,7 +155,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
=== "AWS CloudShell"
After the migration of AWS CloudShell from Amazon Linux 2 to Amazon Linux 2023 [[1]](https://aws.amazon.com/about-aws/whats-new/2023/12/aws-cloudshell-migrated-al2023/) [2](https://docs.aws.amazon.com/cloudshell/latest/userguide/cloudshell-AL2023-migration.html), there is no longer a need to manually compile Python 3.9 as it's already included in AL2023. Prowler can thus be easily installed following the Generic method of installation via pip. Follow the steps below to successfully execute Prowler v4 in AWS CloudShell:
After the migration of AWS CloudShell from Amazon Linux 2 to Amazon Linux 2023 [[1]](https://aws.amazon.com/about-aws/whats-new/2023/12/aws-cloudshell-migrated-al2023/) [[2]](https://docs.aws.amazon.com/cloudshell/latest/userguide/cloudshell-AL2023-migration.html), there is no longer a need to manually compile Python 3.9 as it's already included in AL2023. Prowler can thus be easily installed following the Generic method of installation via pip. Follow the steps below to successfully execute Prowler v4 in AWS CloudShell:
_Requirements_:
@@ -133,11 +163,13 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
_Commands_:
```
```bash
sudo bash
adduser prowler
su prowler
pip install prowler
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install prowler
cd /tmp
prowler aws
```
@@ -153,9 +185,12 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler/),
_Commands_:
```
pip install prowler
prowler -v
```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install prowler
cd /tmp
prowler azure --az-cli-auth
```
## Prowler container versions

View File

@@ -13,52 +13,53 @@ The following list includes all the AWS checks with configurable variables that
| Check Name | Value | Type |
|---------------------------------------------------------------|--------------------------------------------------|-----------------|
| `iam_user_accesskey_unused` | `max_unused_access_keys_days` | Integer |
| `iam_user_console_access_unused` | `max_console_access_days` | Integer |
| `ec2_elastic_ip_shodan` | `shodan_api_key` | String |
| `ec2_securitygroup_with_many_ingress_egress_rules` | `max_security_group_rules` | Integer |
| `ec2_instance_older_than_specific_days` | `max_ec2_instance_age_in_days` | Integer |
| `ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports`| `ec2_sg_high_risk_ports` | List of Integer |
| `vpc_endpoint_connections_trust_boundaries` | `trusted_account_ids` | List of Strings |
| `vpc_endpoint_services_allowed_principals_trust_boundaries` | `trusted_account_ids` | List of Strings |
| `cloudwatch_log_group_retention_policy_specific_days_enabled` | `log_group_retention_days` | Integer |
| `appstream_fleet_session_idle_disconnect_timeout` | `max_idle_disconnect_timeout_in_seconds` | Integer |
| `appstream_fleet_session_disconnect_timeout` | `max_disconnect_timeout_in_seconds` | Integer |
| `appstream_fleet_maximum_session_duration` | `max_session_duration_seconds` | Integer |
| `awslambda_function_using_supported_runtimes` | `obsolete_lambda_runtimes` | Integer |
| `organizations_scp_check_deny_regions` | `organizations_enabled_regions` | List of Strings |
| `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 |
| `codebuild_project_no_secrets_in_variables` | `excluded_sensitive_environment_variables` | 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 |
| `eks_control_plane_logging_all_types_enabled` | `eks_required_log_types` | List of Strings |
| `eks_cluster_uses_a_supported_version` | `eks_cluster_oldest_version_supported` | String |
| `elbv2_is_in_multiple_az` | `elbv2_min_azs` | Integer |
| `elb_is_in_multiple_az` | `elb_min_azs` | Integer |
| `appstream_fleet_maximum_session_duration` | `max_session_duration_seconds` | Integer |
| `appstream_fleet_session_disconnect_timeout` | `max_disconnect_timeout_in_seconds` | Integer |
| `appstream_fleet_session_idle_disconnect_timeout` | `max_idle_disconnect_timeout_in_seconds` | Integer |
| `autoscaling_find_secrets_ec2_launch_configuration` | `secrets_ignore_patterns` | List of Strings |
| `awslambda_function_no_secrets_in_code` | `secrets_ignore_patterns` | List of Strings |
| `awslambda_function_no_secrets_in_variables` | `secrets_ignore_patterns` | List of Strings |
| `awslambda_function_using_supported_runtimes` | `obsolete_lambda_runtimes` | Integer |
| `awslambda_function_vpc_is_in_multi_azs` | `lambda_min_azs` | Integer |
| `cloudformation_stack_outputs_find_secrets` | `secrets_ignore_patterns` | List of Strings |
| `cloudtrail_threat_detection_enumeration` | `threat_detection_enumeration_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_privilege_escalation` | `threat_detection_privilege_escalation_actions` | List of Strings |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_entropy` | Integer |
| `cloudtrail_threat_detection_privilege_escalation` | `threat_detection_privilege_escalation_minutes` | Integer |
| `cloudwatch_log_group_no_secrets_in_logs` | `secrets_ignore_patterns` | List of Strings |
| `cloudwatch_log_group_retention_policy_specific_days_enabled` | `log_group_retention_days` | Integer |
| `codebuild_project_no_secrets_in_variables` | `excluded_sensitive_environment_variables` | List of Strings |
| `codebuild_project_no_secrets_in_variables` | `secrets_ignore_patterns` | List of Strings |
| `config_recorder_all_regions_enabled` | `mute_non_default_regions` | Boolean |
| `drs_job_exist` | `mute_non_default_regions` | Boolean |
| `ec2_elastic_ip_shodan` | `shodan_api_key` | String |
| `ec2_instance_older_than_specific_days` | `max_ec2_instance_age_in_days` | Integer |
| `ec2_instance_secrets_user_data` | `secrets_ignore_patterns` | List of Strings |
| `ec2_launch_template_no_secrets` | `secrets_ignore_patterns` | List of Strings |
| `ec2_securitygroup_allow_ingress_from_internet_to_any_port` | `ec2_allowed_instance_owners` | List of Strings |
| `ec2_securitygroup_allow_ingress_from_internet_to_any_port` | `ec2_allowed_interface_types` | List of Strings |
| `ec2_securitygroup_allow_ingress_from_internet_to_high_risk_tcp_ports`| `ec2_sg_high_risk_ports` | List of Integer |
| `ec2_securitygroup_with_many_ingress_egress_rules` | `max_security_group_rules` | Integer |
| `ecs_task_definitions_no_environment_secrets` | `secrets_ignore_patterns` | List of Strings |
| `ecr_repositories_scan_vulnerabilities_in_latest_image` | `ecr_repository_vulnerability_minimum_severity` | String |
| `eks_cluster_uses_a_supported_version` | `eks_cluster_oldest_version_supported` | String |
| `eks_control_plane_logging_all_types_enabled` | `eks_required_log_types` | List of Strings |
| `elb_is_in_multiple_az` | `elb_min_azs` | Integer |
| `elbv2_is_in_multiple_az` | `elbv2_min_azs` | Integer |
| `guardduty_is_enabled` | `mute_non_default_regions` | Boolean |
| `iam_user_accesskey_unused` | `max_unused_access_keys_days` | Integer |
| `iam_user_console_access_unused` | `max_console_access_days` | Integer |
| `organizations_delegated_administrators` | `organizations_trusted_delegated_administrators` | List of Strings |
| `organizations_scp_check_deny_regions` | `organizations_enabled_regions` | List of Strings |
| `rds_instance_backup_enabled` | `check_rds_instance_replicas` | Boolean |
| `securityhub_enabled` | `mute_non_default_regions` | Boolean |
| `ssm_document_secrets` | `secrets_ignore_patterns` | List of Strings |
| `trustedadvisor_premium_support_plan_subscribed` | `verify_premium_support_plans` | Boolean |
| `vpc_endpoint_connections_trust_boundaries` | `trusted_account_ids` | List of Strings |
| `vpc_endpoint_services_allowed_principals_trust_boundaries` | `trusted_account_ids` | List of Strings |
## Azure
@@ -157,6 +158,7 @@ aws:
]
# AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries)
# AWS SSM Configuration (aws.ssm_documents_set_as_public)
# 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.
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]

2384
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,8 +15,6 @@ from prowler.config.config import (
)
from prowler.lib.banner import print_banner
from prowler.lib.check.check import (
bulk_load_checks_metadata,
bulk_load_compliance_frameworks,
exclude_checks_to_run,
exclude_services_to_run,
execute_checks,
@@ -36,10 +34,12 @@ from prowler.lib.check.check import (
)
from prowler.lib.check.checks_loader import load_checks_to_execute
from prowler.lib.check.compliance import update_checks_metadata_with_compliance
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.check.custom_checks_metadata import (
parse_custom_checks_metadata_file,
update_checks_metadata,
)
from prowler.lib.check.models import CheckMetadata
from prowler.lib.cli.parser import ProwlerArgumentParser
from prowler.lib.logger import logger, set_logging_config
from prowler.lib.outputs.asff.asff import ASFF
@@ -54,6 +54,7 @@ from prowler.lib.outputs.compliance.compliance import display_compliance_table
from prowler.lib.outputs.compliance.ens.ens_aws import AWSENS
from prowler.lib.outputs.compliance.generic.generic import GenericCompliance
from prowler.lib.outputs.compliance.iso27001.iso27001_aws import AWSISO27001
from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp_aws import AWSKISAISMSP
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_aws import AWSMitreAttack
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack_azure import (
AzureMitreAttack,
@@ -68,8 +69,12 @@ from prowler.lib.outputs.slack.slack import Slack
from prowler.lib.outputs.summary_table import display_summary_table
from prowler.providers.aws.lib.s3.s3 import S3
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub
from prowler.providers.aws.models import AWSOutputOptions
from prowler.providers.azure.models import AzureOutputOptions
from prowler.providers.common.provider import Provider
from prowler.providers.common.quick_inventory import run_provider_quick_inventory
from prowler.providers.gcp.models import GCPOutputOptions
from prowler.providers.kubernetes.models import KubernetesOutputOptions
def prowler():
@@ -131,7 +136,7 @@ def prowler():
# Load checks metadata
logger.debug("Loading checks metadata from .metadata.json files")
bulk_checks_metadata = bulk_load_checks_metadata(provider)
bulk_checks_metadata = CheckMetadata.get_bulk(provider)
if args.list_categories:
print_categories(list_categories(bulk_checks_metadata))
@@ -141,7 +146,7 @@ def prowler():
# Load compliance frameworks
logger.debug("Loading compliance frameworks from .json files")
bulk_compliance_frameworks = bulk_load_compliance_frameworks(provider)
bulk_compliance_frameworks = Compliance.get_bulk(provider)
# Complete checks metadata with the compliance framework specification
bulk_checks_metadata = update_checks_metadata_with_compliance(
bulk_compliance_frameworks, bulk_checks_metadata
@@ -190,7 +195,7 @@ def prowler():
sys.exit()
# Provider to scan
Provider.set_global_provider(args)
Provider.init_global_provider(args)
global_provider = Provider.get_global_provider()
# Print Provider Credentials
@@ -224,7 +229,8 @@ def prowler():
# Once the provider is set and we have the eventual checks based on the resource identifier,
# it is time to check what Prowler's checks are going to be executed
checks_from_resources = global_provider.get_checks_to_execute_by_audit_resources()
if checks_from_resources:
# Intersect checks from resources with checks to execute so we only run the checks that apply to the resources with the specified ARNs or tags
if getattr(args, "resource_arn", None) or getattr(args, "resource_tag", None):
checks_to_execute = checks_to_execute.intersection(checks_from_resources)
# Sort final check list
@@ -234,7 +240,22 @@ def prowler():
global_provider.mutelist = args.mutelist_file
# Setup Output Options
global_provider.output_options = (args, bulk_checks_metadata)
if provider == "aws":
output_options = AWSOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
elif provider == "azure":
output_options = AzureOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
elif provider == "gcp":
output_options = GCPOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
elif provider == "kubernetes":
output_options = KubernetesOutputOptions(
args, bulk_checks_metadata, global_provider.identity
)
# Run the quick inventory for the provider if available
if hasattr(args, "quick_inventory") and args.quick_inventory:
@@ -250,6 +271,7 @@ def prowler():
global_provider,
custom_checks_metadata,
args.config_file,
output_options,
)
else:
logger.error(
@@ -257,7 +279,7 @@ def prowler():
)
# Prowler Fixer
if global_provider.output_options.fixer:
if output_options.fixer:
print(f"{Style.BRIGHT}\nRunning Prowler Fixer, please wait...{Style.RESET_ALL}")
# Check if there are any FAIL findings
if any("FAIL" in finding.status for finding in findings):
@@ -303,7 +325,8 @@ def prowler():
# TODO: this part is needed since the checks generates a Check_Report_XXX and the output uses Finding
# This will be refactored for the outputs generate directly the Finding
finding_outputs = [
Finding.generate_output(global_provider, finding) for finding in findings
Finding.generate_output(global_provider, finding, output_options)
for finding in findings
]
generated_outputs = {"regular": [], "compliance": []}
@@ -311,8 +334,8 @@ def prowler():
if args.output_formats:
for mode in args.output_formats:
filename = (
f"{global_provider.output_options.output_directory}/"
f"{global_provider.output_options.output_filename}"
f"{output_options.output_directory}/"
f"{output_options.output_filename}"
)
if mode == "csv":
csv_output = CSV(
@@ -354,16 +377,16 @@ def prowler():
)
# Compliance Frameworks
input_compliance_frameworks = set(
global_provider.output_options.output_modes
).intersection(get_available_compliance_frameworks(provider))
input_compliance_frameworks = set(output_options.output_modes).intersection(
get_available_compliance_frameworks(provider)
)
if provider == "aws":
for compliance_name in input_compliance_frameworks:
if compliance_name.startswith("cis_"):
# Generate CIS Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
cis = AWSCIS(
findings=finding_outputs,
@@ -376,8 +399,8 @@ def prowler():
elif compliance_name == "mitre_attack_aws":
# Generate MITRE ATT&CK Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
mitre_attack = AWSMitreAttack(
findings=finding_outputs,
@@ -390,8 +413,8 @@ def prowler():
elif compliance_name.startswith("ens_"):
# Generate ENS Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
ens = AWSENS(
findings=finding_outputs,
@@ -404,8 +427,8 @@ def prowler():
elif compliance_name.startswith("aws_well_architected_framework"):
# Generate AWS Well-Architected Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
aws_well_architected = AWSWellArchitected(
findings=finding_outputs,
@@ -418,8 +441,8 @@ def prowler():
elif compliance_name.startswith("iso27001_"):
# Generate ISO27001 Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
iso27001 = AWSISO27001(
findings=finding_outputs,
@@ -429,10 +452,24 @@ def prowler():
)
generated_outputs["compliance"].append(iso27001)
iso27001.batch_write_data_to_file()
elif compliance_name.startswith("kisa"):
# Generate KISA-ISMS-P Finding Object
filename = (
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
kisa_ismsp = AWSKISAISMSP(
findings=finding_outputs,
compliance=bulk_compliance_frameworks[compliance_name],
create_file_descriptor=True,
file_path=filename,
)
generated_outputs["compliance"].append(kisa_ismsp)
kisa_ismsp.batch_write_data_to_file()
else:
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
generic_compliance = GenericCompliance(
findings=finding_outputs,
@@ -448,8 +485,8 @@ def prowler():
if compliance_name.startswith("cis_"):
# Generate CIS Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
cis = AzureCIS(
findings=finding_outputs,
@@ -462,8 +499,8 @@ def prowler():
elif compliance_name == "mitre_attack_azure":
# Generate MITRE ATT&CK Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
mitre_attack = AzureMitreAttack(
findings=finding_outputs,
@@ -475,8 +512,8 @@ def prowler():
mitre_attack.batch_write_data_to_file()
else:
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
generic_compliance = GenericCompliance(
findings=finding_outputs,
@@ -492,8 +529,8 @@ def prowler():
if compliance_name.startswith("cis_"):
# Generate CIS Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
cis = GCPCIS(
findings=finding_outputs,
@@ -506,8 +543,8 @@ def prowler():
elif compliance_name == "mitre_attack_gcp":
# Generate MITRE ATT&CK Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
mitre_attack = GCPMitreAttack(
findings=finding_outputs,
@@ -519,8 +556,8 @@ def prowler():
mitre_attack.batch_write_data_to_file()
else:
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
generic_compliance = GenericCompliance(
findings=finding_outputs,
@@ -536,8 +573,8 @@ def prowler():
if compliance_name.startswith("cis_"):
# Generate CIS Finding Object
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
cis = KubernetesCIS(
findings=finding_outputs,
@@ -549,8 +586,8 @@ def prowler():
cis.batch_write_data_to_file()
else:
filename = (
f"{global_provider.output_options.output_directory}/compliance/"
f"{global_provider.output_options.output_filename}_{compliance_name}.csv"
f"{output_options.output_directory}/compliance/"
f"{output_options.output_filename}_{compliance_name}.csv"
)
generic_compliance = GenericCompliance(
findings=finding_outputs,
@@ -593,7 +630,7 @@ def prowler():
aws_partition=global_provider.identity.partition,
aws_session=global_provider.session.current_session,
findings=asff_output.data,
send_only_fails=global_provider.output_options.send_sh_only_fails,
send_only_fails=output_options.send_sh_only_fails,
aws_security_hub_available_regions=security_hub_regions,
)
# Send the findings to Security Hub
@@ -619,7 +656,7 @@ def prowler():
display_summary_table(
findings,
global_provider,
global_provider.output_options,
output_options,
)
# Only display compliance table if there are findings (not all MANUAL) and it is a default execution
if (
@@ -638,13 +675,13 @@ def prowler():
findings,
bulk_checks_metadata,
compliance,
global_provider.output_options.output_filename,
global_provider.output_options.output_directory,
output_options.output_filename,
output_options.output_directory,
compliance_overview,
)
if compliance_overview:
print(
f"\nDetailed compliance results are in {Fore.YELLOW}{global_provider.output_options.output_directory}/compliance/{Style.RESET_ALL}\n"
f"\nDetailed compliance results are in {Fore.YELLOW}{output_options.output_directory}/compliance/{Style.RESET_ALL}\n"
)
# If custom checks were passed, remove the modules

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "4.4.0"
prowler_version = "4.5.0"
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"

View File

@@ -57,7 +57,13 @@ aws:
8088,
]
# AWS ECS Configuration
# aws.ecs_service_fargate_latest_platform_version
fargate_linux_latest_version: "1.4.0"
fargate_windows_latest_version: "1.0.0"
# AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries)
# AWS SSM Configuration (aws.ssm_documents_set_as_public)
# 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.
# trusted_account_ids : ["123456789012", "098765432109", "678901234567"]
@@ -101,6 +107,8 @@ aws:
"ruby2.5",
"ruby2.7",
]
# aws.awslambda_function_vpc_is_in_multi_azs
lambda_min_azs: 2
# AWS Organizations
# aws.organizations_scp_check_deny_regions
@@ -327,8 +335,8 @@ aws:
elbv2_min_azs: 2
# Known secrets to ignore on detection
# this will include a list of regex patterns to ignore on detection
# AWS Secrets Configuration
# Patterns to ignore in the secrets checks
secrets_ignore_patterns: []
# Azure Configuration

View File

@@ -5,8 +5,6 @@ class ProwlerException(Exception):
(1901, "UnexpectedError"): {
"message": "Unexpected error occurred.",
"remediation": "Please review the error message and try again.",
"file": "{file}",
"provider": "{provider}",
}
}

View File

@@ -6,7 +6,6 @@ import re
import shutil
import sys
import traceback
from pkgutil import walk_packages
from types import ModuleType
from typing import Any
@@ -15,76 +14,15 @@ from colorama import Fore, Style
import prowler
from prowler.config.config import orange_color
from prowler.lib.check.compliance_models import load_compliance_framework
from prowler.lib.check.custom_checks_metadata import update_check_metadata
from prowler.lib.check.models import Check, CheckMetadata, load_check_metadata
from prowler.lib.check.models import Check
from prowler.lib.check.utils import recover_checks_from_provider
from prowler.lib.logger import logger
from prowler.lib.outputs.outputs import report
from prowler.lib.utils.utils import open_file, parse_json_file, print_boxes
from prowler.providers.common.models import Audit_Metadata
# Load all checks metadata
def bulk_load_checks_metadata(provider: str) -> dict[str, CheckMetadata]:
"""
Load the metadata of all checks for a given provider reading the check's metadata files.
Args:
provider (str): The name of the provider.
Returns:
dict[str, CheckMetadata]: A dictionary containing the metadata of all checks, with the CheckID as the key.
"""
bulk_check_metadata = {}
checks = recover_checks_from_provider(provider)
# Build list of check's metadata files
for check_info in checks:
# Build check path name
check_name = check_info[0]
check_path = check_info[1]
# Ignore fixer files
if check_name.endswith("_fixer"):
continue
# Append metadata file extension
metadata_file = f"{check_path}/{check_name}.metadata.json"
# Load metadata
check_metadata = load_check_metadata(metadata_file)
bulk_check_metadata[check_metadata.CheckID] = check_metadata
return bulk_check_metadata
# Bulk load all compliance frameworks specification
def bulk_load_compliance_frameworks(provider: str) -> dict:
"""Bulk load all compliance frameworks specification into a dict"""
try:
bulk_compliance_frameworks = {}
available_compliance_framework_modules = list_compliance_modules()
for compliance_framework in available_compliance_framework_modules:
if provider in compliance_framework.name:
compliance_specification_dir_path = (
f"{compliance_framework.module_finder.path}/{provider}"
)
# for compliance_framework in available_compliance_framework_modules:
for filename in os.listdir(compliance_specification_dir_path):
file_path = os.path.join(
compliance_specification_dir_path, filename
)
# Check if it is a file and ti size is greater than 0
if os.path.isfile(file_path) and os.stat(file_path).st_size > 0:
# Open Compliance file in JSON
# cis_v1.4_aws.json --> cis_v1.4_aws
compliance_framework_name = filename.split(".json")[0]
# Store the compliance info
bulk_compliance_frameworks[compliance_framework_name] = (
load_compliance_framework(file_path)
)
except Exception as e:
logger.error(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}] -- {e}")
return bulk_compliance_frameworks
# Exclude checks to run
def exclude_checks_to_run(checks_to_execute: set, excluded_checks: list) -> set:
for check in excluded_checks:
@@ -381,100 +319,12 @@ def parse_checks_from_compliance_framework(
return checks_to_execute
def recover_checks_from_provider(
provider: str, service: str = None, include_fixers: bool = False
) -> list[tuple]:
"""
Recover all checks from the selected provider and service
Returns a list of tuples with the following format (check_name, check_path)
"""
try:
checks = []
modules = list_modules(provider, service)
for module_name in modules:
# Format: "prowler.providers.{provider}.services.{service}.{check_name}.{check_name}"
check_module_name = module_name.name
# We need to exclude common shared libraries in services
if (
check_module_name.count(".") == 6
and "lib" not in check_module_name
and (not check_module_name.endswith("_fixer") or include_fixers)
):
check_path = module_name.module_finder.path
# Check name is the last part of the check_module_name
check_name = check_module_name.split(".")[-1]
check_info = (check_name, check_path)
checks.append(check_info)
except ModuleNotFoundError:
logger.critical(f"Service {service} was not found for the {provider} provider.")
sys.exit(1)
except Exception as e:
logger.critical(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}")
sys.exit(1)
else:
return checks
def list_compliance_modules():
"""
list_compliance_modules returns the available compliance frameworks and returns their path
"""
# This module path requires the full path including "prowler."
module_path = "prowler.compliance"
return walk_packages(
importlib.import_module(module_path).__path__,
importlib.import_module(module_path).__name__ + ".",
)
# List all available modules in the selected provider and service
def list_modules(provider: str, service: str):
# This module path requires the full path including "prowler."
module_path = f"prowler.providers.{provider}.services"
if service:
module_path += f".{service}"
return walk_packages(
importlib.import_module(module_path).__path__,
importlib.import_module(module_path).__name__ + ".",
)
# Import an input check using its path
def import_check(check_path: str) -> ModuleType:
lib = importlib.import_module(f"{check_path}")
return lib
def run_check(check: Check, verbose: bool = False, only_logs: bool = False) -> list:
"""
Run the check and return the findings
Args:
check (Check): check class
output_options (Any): output options
Returns:
list: list of findings
"""
findings = []
if verbose:
print(
f"\nCheck ID: {check.CheckID} - {Fore.MAGENTA}{check.ServiceName}{Fore.YELLOW} [{check.Severity}]{Style.RESET_ALL}"
)
logger.debug(f"Executing check: {check.CheckID}")
try:
findings = check.execute()
except Exception as error:
if not only_logs:
print(
f"Something went wrong in {check.CheckID}, please use --log-level ERROR"
)
logger.error(
f"{check.CheckID} -- {error.__class__.__name__}[{traceback.extract_tb(error.__traceback__)[-1].lineno}]: {error}"
)
finally:
return findings
def run_fixer(check_findings: list) -> int:
"""
Run the fixer for the check if it exists and there are any FAIL findings
@@ -556,6 +406,7 @@ def execute_checks(
global_provider: Any,
custom_checks_metadata: Any,
config_file: str,
output_options: Any,
) -> list:
# List to store all the check's findings
all_findings = []
@@ -591,18 +442,42 @@ def execute_checks(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
# Set verbose flag
verbose = False
if hasattr(output_options, "verbose"):
verbose = output_options.verbose
elif hasattr(output_options, "fixer"):
verbose = output_options.fixer
# Execution with the --only-logs flag
if global_provider.output_options.only_logs:
if output_options.only_logs:
for check_name in checks_to_execute:
# Recover service from check name
service = check_name.split("_")[0]
try:
try:
# Import check module
check_module_path = f"prowler.providers.{global_provider.type}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)
check = check_to_execute()
except ModuleNotFoundError:
logger.error(
f"Check '{check_name}' was not found for the {global_provider.type.upper()} provider"
)
continue
if verbose:
print(
f"\nCheck ID: {check.CheckID} - {Fore.MAGENTA}{check.ServiceName}{Fore.YELLOW} [{check.Severity}]{Style.RESET_ALL}"
)
check_findings = execute(
service,
check_name,
check,
global_provider,
custom_checks_metadata,
output_options,
)
report(check_findings, global_provider, output_options)
all_findings.extend(check_findings)
# Update Audit Status
@@ -660,12 +535,31 @@ def execute_checks(
f"-> Scanning {orange_color}{service}{Style.RESET_ALL} service"
)
try:
try:
# Import check module
check_module_path = f"prowler.providers.{global_provider.type}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)
check = check_to_execute()
except ModuleNotFoundError:
logger.error(
f"Check '{check_name}' was not found for the {global_provider.type.upper()} provider"
)
continue
if verbose:
print(
f"\nCheck ID: {check.CheckID} - {Fore.MAGENTA}{check.ServiceName}{Fore.YELLOW} [{check.Severity}]{Style.RESET_ALL}"
)
check_findings = execute(
service,
check_name,
check,
global_provider,
custom_checks_metadata,
output_options,
)
report(check_findings, global_provider, output_options)
all_findings.extend(check_findings)
services_executed.add(service)
checks_executed.add(check_name)
@@ -688,51 +582,79 @@ def execute_checks(
)
bar()
bar.title = f"-> {Fore.GREEN}Scan completed!{Style.RESET_ALL}"
# Custom report interface
if os.environ.get("PROWLER_REPORT_LIB_PATH"):
try:
logger.info("Using custom report interface ...")
lib = os.environ["PROWLER_REPORT_LIB_PATH"]
outputs_module = importlib.import_module(lib)
custom_report_interface = getattr(outputs_module, "report")
# TODO: review this call and see if we can remove the global_provider.output_options since it is contained in the global_provider
custom_report_interface(check_findings, output_options, global_provider)
except Exception:
sys.exit(1)
return all_findings
def execute(
service: str,
check_name: str,
check: Check,
global_provider: Any,
custom_checks_metadata: Any,
output_options: Any = None,
):
try:
# Import check module
check_module_path = f"prowler.providers.{global_provider.type}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)
check_class = check_to_execute()
"""
Execute the check and report the findings
Args:
service (str): service name
check_name (str): check name
global_provider (Any): provider object
custom_checks_metadata (Any): custom checks metadata
output_options (Any): output options, depending on the provider
Returns:
list: list of findings
"""
try:
# Update check metadata to reflect that in the outputs
if custom_checks_metadata and custom_checks_metadata["Checks"].get(
check_class.CheckID
check.CheckID
):
check_class = update_check_metadata(
check_class, custom_checks_metadata["Checks"][check_class.CheckID]
check = update_check_metadata(
check, custom_checks_metadata["Checks"][check.CheckID]
)
# Run check
verbose = (
global_provider.output_options.verbose
or global_provider.output_options.fixer
)
check_findings = run_check(
check_class, verbose, global_provider.output_options.only_logs
)
only_logs = False
if hasattr(output_options, "only_logs"):
only_logs = output_options.only_logs
# Execute the check
check_findings = []
logger.debug(f"Executing check: {check.CheckID}")
try:
check_findings = check.execute()
except Exception as error:
if not only_logs:
print(
f"Something went wrong in {check.CheckID}, please use --log-level ERROR"
)
logger.error(
f"{check.CheckID} -- {error.__class__.__name__}[{traceback.extract_tb(error.__traceback__)[-1].lineno}]: {error}"
)
# Exclude findings per status
if global_provider.output_options.status:
if hasattr(output_options, "status") and output_options.status:
check_findings = [
finding
for finding in check_findings
if finding.status in global_provider.output_options.status
if finding.status in output_options.status
]
# Mutelist findings
# Before returning the findings, we need to apply the mute list logic
if hasattr(global_provider, "mutelist") and global_provider.mutelist.mutelist:
# TODO: make this prettier
is_finding_muted_args = {}
if global_provider.type == "aws":
is_finding_muted_args["aws_account_id"] = (
@@ -747,27 +669,9 @@ def execute(
**is_finding_muted_args
)
# Refactor(Outputs)
# Report the check's findings
report(check_findings, global_provider)
# Refactor(Outputs)
if os.environ.get("PROWLER_REPORT_LIB_PATH"):
try:
logger.info("Using custom report interface ...")
lib = os.environ["PROWLER_REPORT_LIB_PATH"]
outputs_module = importlib.import_module(lib)
custom_report_interface = getattr(outputs_module, "report")
# TODO: review this call and see if we can remove the global_provider.output_options since it is contained in the global_provider
custom_report_interface(
check_findings, global_provider.output_options, global_provider
)
except Exception:
sys.exit(1)
except ModuleNotFoundError:
logger.error(
f"Check '{check_name}' was not found for the {global_provider.type.upper()} provider"
f"Check '{check.CheckID}' was not found for the {global_provider.type.upper()} provider"
)
check_findings = []
except Exception as error:
@@ -797,34 +701,3 @@ def update_audit_metadata(
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def recover_checks_from_service(service_list: list, provider: str) -> set:
"""
Recover all checks from the selected provider and service
Returns a set of checks from the given services
"""
try:
checks = set()
service_list = [
"awslambda" if service == "lambda" else service for service in service_list
]
for service in service_list:
service_checks = recover_checks_from_provider(provider, service)
if not service_checks:
logger.error(f"Service '{service}' does not have checks.")
else:
for check in service_checks:
# Recover check name and module name from import path
# Format: "providers.{provider}.services.{service}.{check_name}.{check_name}"
check_name = check[0].split(".")[-1]
# If the service is present in the group list passed as parameters
# if service_name in group_list: checks_from_arn.add(check_name)
checks.add(check_name)
return checks
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)

View File

@@ -4,6 +4,8 @@ from prowler.config.config import valid_severities
from prowler.lib.check.check import (
parse_checks_from_compliance_framework,
parse_checks_from_file,
)
from prowler.lib.check.utils import (
recover_checks_from_provider,
recover_checks_from_service,
)

View File

@@ -1,9 +1,11 @@
import os
import sys
from enum import Enum
from typing import Optional, Union
from pydantic import BaseModel, ValidationError, root_validator
from prowler.lib.check.utils import list_compliance_modules
from prowler.lib.logger import logger
@@ -167,6 +169,19 @@ class Mitre_Requirement(BaseModel):
Checks: list[str]
# KISA-ISMS-P Requirement Attribute
class KISA_ISMSP_Requirement_Attribute(BaseModel):
"""KISA ISMS-P Requirement Attribute"""
Domain: str
Subdomain: str
Section: str
AuditChecklist: Optional[list[str]]
RelatedRegulations: Optional[list[str]]
AuditEvidence: Optional[list[str]]
NonComplianceCases: Optional[list[str]]
# Base Compliance Model
# TODO: move this to compliance folder
class Compliance_Requirement(BaseModel):
@@ -181,6 +196,7 @@ class Compliance_Requirement(BaseModel):
ENS_Requirement_Attribute,
ISO27001_2013_Requirement_Attribute,
AWS_Well_Architected_Requirement_Attribute,
KISA_ISMSP_Requirement_Attribute,
# Generic_Compliance_Requirement_Attribute must be the last one since it is the fallback for generic compliance framework
Generic_Compliance_Requirement_Attribute,
]
@@ -306,6 +322,36 @@ class Compliance(BaseModel):
return requirement
@staticmethod
def get_bulk(provider: str) -> dict:
"""Bulk load all compliance frameworks specification into a dict"""
try:
bulk_compliance_frameworks = {}
available_compliance_framework_modules = list_compliance_modules()
for compliance_framework in available_compliance_framework_modules:
if provider in compliance_framework.name:
compliance_specification_dir_path = (
f"{compliance_framework.module_finder.path}/{provider}"
)
# for compliance_framework in available_compliance_framework_modules:
for filename in os.listdir(compliance_specification_dir_path):
file_path = os.path.join(
compliance_specification_dir_path, filename
)
# Check if it is a file and ti size is greater than 0
if os.path.isfile(file_path) and os.stat(file_path).st_size > 0:
# Open Compliance file in JSON
# cis_v1.4_aws.json --> cis_v1.4_aws
compliance_framework_name = filename.split(".json")[0]
# Store the compliance info
bulk_compliance_frameworks[compliance_framework_name] = (
load_compliance_framework(file_path)
)
except Exception as e:
logger.error(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}] -- {e}")
return bulk_compliance_frameworks
# Testing Pending
def load_compliance_framework(

View File

@@ -7,6 +7,7 @@ from dataclasses import dataclass
from pydantic import BaseModel, ValidationError, validator
from prowler.config.config import valid_severities
from prowler.lib.check.utils import recover_checks_from_provider
from prowler.lib.logger import logger
@@ -129,6 +130,34 @@ class CheckMetadata(BaseModel):
)
return severity
@staticmethod
def get_bulk(provider: str) -> dict[str, "CheckMetadata"]:
"""
Load the metadata of all checks for a given provider reading the check's metadata files.
Args:
provider (str): The name of the provider.
Returns:
dict[str, CheckMetadata]: A dictionary containing the metadata of all checks, with the CheckID as the key.
"""
bulk_check_metadata = {}
checks = recover_checks_from_provider(provider)
# Build list of check's metadata files
for check_info in checks:
# Build check path name
check_name = check_info[0]
check_path = check_info[1]
# Ignore fixer files
if check_name.endswith("_fixer"):
continue
# Append metadata file extension
metadata_file = f"{check_path}/{check_name}.metadata.json"
# Load metadata
check_metadata = load_check_metadata(metadata_file)
bulk_check_metadata[check_metadata.CheckID] = check_metadata
return bulk_check_metadata
class Check(ABC, CheckMetadata):
"""Prowler Check"""

View File

@@ -0,0 +1,95 @@
import importlib
import sys
from pkgutil import walk_packages
from prowler.lib.logger import logger
def recover_checks_from_provider(
provider: str, service: str = None, include_fixers: bool = False
) -> list[tuple]:
"""
Recover all checks from the selected provider and service
Returns a list of tuples with the following format (check_name, check_path)
"""
try:
checks = []
modules = list_modules(provider, service)
for module_name in modules:
# Format: "prowler.providers.{provider}.services.{service}.{check_name}.{check_name}"
check_module_name = module_name.name
# We need to exclude common shared libraries in services
if (
check_module_name.count(".") == 6
and "lib" not in check_module_name
and (not check_module_name.endswith("_fixer") or include_fixers)
):
check_path = module_name.module_finder.path
# Check name is the last part of the check_module_name
check_name = check_module_name.split(".")[-1]
check_info = (check_name, check_path)
checks.append(check_info)
except ModuleNotFoundError:
logger.critical(f"Service {service} was not found for the {provider} provider.")
sys.exit(1)
except Exception as e:
logger.critical(f"{e.__class__.__name__}[{e.__traceback__.tb_lineno}]: {e}")
sys.exit(1)
else:
return checks
# List all available modules in the selected provider and service
def list_modules(provider: str, service: str):
# This module path requires the full path including "prowler."
module_path = f"prowler.providers.{provider}.services"
if service:
module_path += f".{service}"
return walk_packages(
importlib.import_module(module_path).__path__,
importlib.import_module(module_path).__name__ + ".",
)
def recover_checks_from_service(service_list: list, provider: str) -> set:
"""
Recover all checks from the selected provider and service
Returns a set of checks from the given services
"""
try:
checks = set()
service_list = [
"awslambda" if service == "lambda" else service for service in service_list
]
for service in service_list:
service_checks = recover_checks_from_provider(provider, service)
if not service_checks:
logger.error(f"Service '{service}' does not have checks.")
else:
for check in service_checks:
# Recover check name and module name from import path
# Format: "providers.{provider}.services.{service}.{check_name}.{check_name}"
check_name = check[0].split(".")[-1]
# If the service is present in the group list passed as parameters
# if service_name in group_list: checks_from_arn.add(check_name)
checks.add(check_name)
return checks
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def list_compliance_modules():
"""
list_compliance_modules returns the available compliance frameworks and returns their path
"""
# This module path requires the full path including "prowler."
module_path = "prowler.compliance"
return walk_packages(
importlib.import_module(module_path).__path__,
importlib.import_module(module_path).__name__ + ".",
)

View File

@@ -89,7 +89,11 @@ class ASFF(Output):
CreatedAt=timestamp,
Severity=Severity(Label=finding.severity.value),
Title=finding.check_title,
Description=finding.description,
Description=(
(finding.status_extended[:1000] + "...")
if len(finding.status_extended) > 1000
else finding.status_extended
),
Resources=[
Resource(
Id=finding.resource_uid,

View File

@@ -7,6 +7,7 @@ from prowler.lib.outputs.compliance.ens.ens import get_ens_table
from prowler.lib.outputs.compliance.generic.generic_table import (
get_generic_compliance_table,
)
from prowler.lib.outputs.compliance.kisa_ismsp.kisa_ismsp import get_kisa_ismsp_table
from prowler.lib.outputs.compliance.mitre_attack.mitre_attack import (
get_mitre_attack_table,
)
@@ -62,6 +63,15 @@ def display_compliance_table(
output_directory,
compliance_overview,
)
elif "kisa_isms_" in compliance_framework:
get_kisa_ismsp_table(
findings,
bulk_checks_metadata,
compliance_framework,
output_filename,
output_directory,
compliance_overview,
)
else:
get_generic_compliance_table(
findings,

View File

@@ -0,0 +1,89 @@
from colorama import Fore, Style
from tabulate import tabulate
from prowler.config.config import orange_color
def get_kisa_ismsp_table(
findings: list,
bulk_checks_metadata: dict,
compliance_framework: str,
output_filename: str,
output_directory: str,
compliance_overview: bool,
):
sections = {}
kisa_ismsp_compliance_table = {
"Provider": [],
"Section": [],
"Status": [],
"Muted": [],
}
pass_count = []
fail_count = []
muted_count = []
for index, finding in enumerate(findings):
check = bulk_checks_metadata[finding.check_metadata.CheckID]
check_compliances = check.Compliance
for compliance in check_compliances:
if (
compliance.Framework.startswith("KISA")
and compliance.Version in compliance_framework
):
for requirement in compliance.Requirements:
for attribute in requirement.Attributes:
section = attribute.Section
# Check if Section exists
if section not in sections:
sections[section] = {
"Status": f"{Fore.GREEN}PASS{Style.RESET_ALL}",
"Muted": 0,
}
if finding.muted:
if index not in muted_count:
muted_count.append(index)
sections[section]["Muted"] += 1
else:
if finding.status == "FAIL" and index not in fail_count:
fail_count.append(index)
elif finding.status == "PASS" and index not in pass_count:
pass_count.append(index)
# Add results to table
sections = dict(sorted(sections.items()))
for section in sections:
kisa_ismsp_compliance_table["Provider"].append(compliance.Provider)
kisa_ismsp_compliance_table["Section"].append(section)
kisa_ismsp_compliance_table["Muted"].append(
f"{orange_color}{sections[section]['Muted']}{Style.RESET_ALL}"
)
if len(fail_count) + len(pass_count) + len(muted_count) > 1:
print(
f"\nCompliance Status of {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Framework:"
)
overview_table = [
[
f"{Fore.RED}{round(len(fail_count) / len(findings) * 100, 2)}% ({len(fail_count)}) FAIL{Style.RESET_ALL}",
f"{Fore.GREEN}{round(len(pass_count) / len(findings) * 100, 2)}% ({len(pass_count)}) PASS{Style.RESET_ALL}",
f"{orange_color}{round(len(muted_count) / len(findings) * 100, 2)}% ({len(muted_count)}) MUTED{Style.RESET_ALL}",
]
]
print(tabulate(overview_table, tablefmt="rounded_grid"))
if not compliance_overview:
print(
f"\nFramework {Fore.YELLOW}{compliance_framework.upper()}{Style.RESET_ALL} Results:"
)
print(
tabulate(
kisa_ismsp_compliance_table,
headers="keys",
tablefmt="rounded_grid",
)
)
print(
f"{Style.BRIGHT}* Only sections containing results appear.{Style.RESET_ALL}"
)
print(f"\nDetailed results of {compliance_framework.upper()} are in:")
print(
f" - CSV: {output_directory}/compliance/{output_filename}_{compliance_framework}.csv\n"
)

View File

@@ -0,0 +1,93 @@
from prowler.lib.check.compliance_models import Compliance
from prowler.lib.outputs.compliance.compliance_output import ComplianceOutput
from prowler.lib.outputs.compliance.kisa_ismsp.models import AWSKISAISMSPModel
from prowler.lib.outputs.finding import Finding
class AWSKISAISMSP(ComplianceOutput):
"""
This class represents the AWS KISA-ISMS-P compliance output.
Attributes:
- _data (list): A list to store transformed data from findings.
- _file_descriptor (TextIOWrapper): A file descriptor to write data to a file.
Methods:
- transform: Transforms findings into AWS KISA-ISMS-P compliance format.
"""
def transform(
self,
findings: list[Finding],
compliance: Compliance,
compliance_name: str,
) -> None:
"""
Transforms a list of findings into AWS KISA-ISMS-P compliance format.
Parameters:
- findings (list): A list of findings.
- compliance (Compliance): A compliance model.
- compliance_name (str): The name of the compliance model.
Returns:
- None
"""
for finding in findings:
# Get the compliance requirements for the finding
finding_requirements = finding.compliance.get(compliance_name, [])
for requirement in compliance.Requirements:
if requirement.Id in finding_requirements:
for attribute in requirement.Attributes:
compliance_row = AWSKISAISMSPModel(
Provider=finding.provider,
Description=compliance.Description,
AccountId=finding.account_uid,
Region=finding.region,
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Name=requirement.Name,
Requirements_Description=requirement.Description,
Requirements_Attributes_Domain=attribute.Domain,
Requirements_Attributes_Subdomain=attribute.Subdomain,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_AuditChecklist=attribute.AuditChecklist,
Requirements_Attributes_RelatedRegulations=attribute.RelatedRegulations,
Requirements_Attributes_AuditEvidence=attribute.AuditEvidence,
Requirements_Attributes_NonComplianceCases=attribute.NonComplianceCases,
Status=finding.status,
StatusExtended=finding.status_extended,
ResourceId=finding.resource_uid,
ResourceName=finding.resource_name,
CheckId=finding.check_id,
Muted=finding.muted,
)
self._data.append(compliance_row)
# Add manual requirements to the compliance output
for requirement in compliance.Requirements:
if not requirement.Checks:
for attribute in requirement.Attributes:
compliance_row = AWSKISAISMSPModel(
Provider=compliance.Provider.lower(),
Description=compliance.Description,
AccountId="",
Region="",
AssessmentDate=str(finding.timestamp),
Requirements_Id=requirement.Id,
Requirements_Name=requirement.Name,
Requirements_Description=requirement.Description,
Requirements_Attributes_Domain=attribute.Domain,
Requirements_Attributes_Subdomain=attribute.Subdomain,
Requirements_Attributes_Section=attribute.Section,
Requirements_Attributes_AuditChecklist=attribute.AuditChecklist,
Requirements_Attributes_RelatedRegulations=attribute.RelatedRegulations,
Requirements_Attributes_AuditEvidence=attribute.AuditEvidence,
Requirements_Attributes_NonComplianceCases=attribute.NonComplianceCases,
Status="MANUAL",
StatusExtended="Manual check",
ResourceId="manual_check",
ResourceName="Manual check",
CheckId="manual",
Muted=False,
)
self._data.append(compliance_row)

View File

@@ -0,0 +1,31 @@
from typing import Optional
from pydantic import BaseModel
class AWSKISAISMSPModel(BaseModel):
"""
The AWS KISA-ISMS-P Model outputs findings in a format compliant with the AWS KISA-ISMS-P standard
"""
Provider: str
Description: str
AccountId: str
Region: str
AssessmentDate: str
Requirements_Id: str
Requirements_Name: str
Requirements_Description: str
Requirements_Attributes_Domain: str
Requirements_Attributes_Subdomain: str
Requirements_Attributes_Section: str
Requirements_Attributes_AuditChecklist: Optional[list[str]]
Requirements_Attributes_RelatedRegulations: Optional[list[str]]
Requirements_Attributes_AuditEvidence: Optional[list[str]]
Requirements_Attributes_NonComplianceCases: Optional[list[str]]
Status: str
StatusExtended: str
ResourceId: str
ResourceName: str
CheckId: str
Muted: bool

View File

@@ -88,29 +88,37 @@ class Finding(BaseModel):
@classmethod
def generate_output(
cls, provider: Provider, check_output: Check_Report
cls, provider: Provider, check_output: Check_Report, output_options
) -> "Finding":
"""Generates the output for a finding based on the provider and output options
Args:
provider (Provider): the provider object
check_output (Check_Report): the check output object
output_options: the output options object, depending on the provider
Returns:
finding_output (Finding): the finding output object
"""
output_options = provider.output_options
# TODO: think about get_provider_data_mapping
provider_data_mapping = get_provider_data_mapping(provider)
# TODO: move fill_common_finding_data
common_finding_data = fill_common_finding_data(
check_output, output_options.unix_timestamp
)
unix_timestamp = False
if hasattr(output_options, "unix_timestamp"):
unix_timestamp = output_options.unix_timestamp
common_finding_data = fill_common_finding_data(check_output, unix_timestamp)
output_data = {}
output_data.update(provider_data_mapping)
output_data.update(common_finding_data)
bulk_checks_metadata = {}
if hasattr(output_options, "bulk_checks_metadata"):
bulk_checks_metadata = output_options.bulk_checks_metadata
output_data["compliance"] = get_check_compliance(
check_output, provider.type, output_options.bulk_checks_metadata
check_output, provider.type, bulk_checks_metadata
)
try:
if provider.type == "aws":

View File

@@ -9,7 +9,6 @@ from py_ocsf_models.events.findings.detection_finding import (
from py_ocsf_models.events.findings.finding import ActivityID, FindingInformation
from py_ocsf_models.objects.account import Account, TypeID
from py_ocsf_models.objects.cloud import Cloud
from py_ocsf_models.objects.container import Container
from py_ocsf_models.objects.group import Group
from py_ocsf_models.objects.metadata import Metadata
from py_ocsf_models.objects.organization import Organization
@@ -37,7 +36,7 @@ class OCSF(Output):
- transform(findings: List[Finding]) -> None: Transforms the findings into the OCSF Detection Finding format.
- batch_write_data_to_file() -> None: Writes the findings to a file using the OCSF Detection Finding format using the `Output._file_descriptor`.
- get_account_type_id_by_provider(provider: str) -> TypeID: Returns the TypeID based on the provider.
- get_finding_status_id(status: str, muted: bool) -> StatusID: Returns the StatusID based on the status and muted values.
- get_finding_status_id(muted: bool) -> StatusID: Returns the StatusID based on the muted value.
References:
- OCSF: https://schema.ocsf.io/1.2.0/classes/detection_finding
@@ -59,21 +58,24 @@ class OCSF(Output):
finding_severity = getattr(
SeverityID, finding.severity.capitalize(), SeverityID.Unknown
)
finding_status = self.get_finding_status_id(
finding.status, finding.muted
)
finding_status = self.get_finding_status_id(finding.muted)
detection_finding = DetectionFinding(
message=finding.status_extended,
activity_id=finding_activity.value,
activity_name=finding_activity.name,
finding_info=FindingInformation(
created_time=finding.timestamp,
created_time_dt=finding.timestamp,
created_time=int(finding.timestamp.timestamp()),
desc=finding.description,
title=finding.check_title,
uid=finding.finding_uid,
name=finding.resource_name,
product_uid="prowler",
types=[finding.check_type],
),
event_time=finding.timestamp,
time_dt=finding.timestamp,
time=int(finding.timestamp.timestamp()),
remediation=Remediation(
desc=finding.remediation_recommendation_text,
references=list(
@@ -96,31 +98,51 @@ class OCSF(Output):
status_code=finding.status,
status_detail=finding.status_extended,
risk_details=finding.risk,
resources=[
ResourceDetails(
labels=unroll_dict_to_list(finding.resource_tags),
name=finding.resource_name,
uid=finding.resource_uid,
group=Group(name=finding.service_name),
type=finding.resource_type,
# TODO: this should be included only if using the Cloud profile
cloud_partition=finding.partition,
region=finding.region,
data={"details": finding.resource_details},
)
],
resources=(
[
ResourceDetails(
labels=unroll_dict_to_list(finding.resource_tags),
name=finding.resource_name,
uid=finding.resource_uid,
group=Group(name=finding.service_name),
type=finding.resource_type,
# TODO: this should be included only if using the Cloud profile
cloud_partition=finding.partition,
region=finding.region,
data={"details": finding.resource_details},
)
]
if finding.provider != "kubernetes"
else [
ResourceDetails(
labels=unroll_dict_to_list(finding.resource_tags),
name=finding.resource_name,
uid=finding.resource_uid,
group=Group(name=finding.service_name),
type=finding.resource_type,
data={"details": finding.resource_details},
namespace=finding.region.replace("namespace: ", ""),
)
]
),
metadata=Metadata(
event_code=finding.check_id,
product=Product(
uid="prowler",
name="Prowler",
vendor_name="Prowler",
version=finding.prowler_version,
),
profiles=(
["cloud", "datetime"]
if finding.provider != "kubernetes"
else ["container", "datetime"]
),
tenant_uid=finding.account_organization_uid,
),
type_uid=DetectionFindingTypeID.Create,
type_name=DetectionFindingTypeID.Create.name,
type_name=f"Detection Finding: {DetectionFindingTypeID.Create.name}",
unmapped={
"check_type": finding.check_type,
"related_url": finding.related_url,
"categories": finding.categories,
"depends_on": finding.depends_on,
@@ -129,26 +151,19 @@ class OCSF(Output):
"compliance": finding.compliance,
},
)
if finding.provider == "kubernetes":
detection_finding.container = Container(
name=finding.resource_name,
uid=finding.resource_uid,
)
# TODO: Get the PID of the namespace (we only have the name of the namespace)
# detection_finding.namespace_pid=,
else:
if finding.provider != "kubernetes":
detection_finding.cloud = Cloud(
account=Account(
name=finding.account_name,
type_id=cloud_account_type.value,
type=cloud_account_type.name,
type=cloud_account_type.name.replace("_", " "),
uid=finding.account_uid,
labels=unroll_dict_to_list(finding.account_tags),
),
org=Organization(
uid=finding.account_organization_uid,
name=finding.account_organization_name,
# TODO: add the org unit id and name
),
provider=finding.provider,
region=finding.region,
@@ -208,20 +223,17 @@ class OCSF(Output):
return type_id
@staticmethod
def get_finding_status_id(status: str, muted: bool) -> StatusID:
def get_finding_status_id(muted: bool) -> StatusID:
"""
Returns the StatusID based on the status and muted values.
Returns the StatusID based on the muted value.
Args:
status (str): The status value
muted (bool): The muted value
Returns:
StatusID: The StatusID based on the status and muted values
StatusID: The StatusID based on the muted value
"""
status_id = StatusID.Other
if status == "FAIL":
status_id = StatusID.New
status_id = StatusID.New
if muted:
status_id = StatusID.Suppressed
return status_id

View File

@@ -25,10 +25,12 @@ def stdout_report(finding, color, verbose, status, fix):
)
# TODO: Only pass check_findings, provider.output_options and provider.type
def report(check_findings, provider):
# TODO: Only pass check_findings, output_options and provider.type
def report(check_findings, provider, output_options):
try:
output_options = provider.output_options
verbose = False
if hasattr(output_options, "verbose"):
verbose = output_options.verbose
if check_findings:
# TO-DO Generic Function
if provider.type == "aws":
@@ -39,21 +41,27 @@ def report(check_findings, provider):
for finding in check_findings:
# Print findings by stdout
status = []
if hasattr(output_options, "status"):
status = output_options.status
fixer = False
if hasattr(output_options, "fixer"):
fixer = output_options.fixer
color = set_report_color(finding.status, finding.muted)
stdout_report(
finding,
color,
output_options.verbose,
output_options.status,
output_options.fixer,
verbose,
status,
fixer,
)
else: # No service resources in the whole account
color = set_report_color("MANUAL")
if output_options.verbose:
if verbose:
print(f"\t{color}INFO{Style.RESET_ALL} There are no resources")
# Separator between findings and bar
if output_options.verbose:
if verbose:
print()
except Exception as error:
logger.error(
@@ -82,9 +90,14 @@ def extract_findings_statistics(findings: list) -> dict:
extract_findings_statistics takes a list of findings and returns the following dict with the aggregated statistics
{
"total_pass": 0,
"total_muted_pass": 0,
"total_fail": 0,
"total_muted_fail": 0,
"resources_count": 0,
"findings_count": 0,
"critical_failed_findings": [],
"critical_passed_findings": []
"all_fails_are_muted": False
}
"""
logger.info("Extracting audit statistics...")
@@ -96,18 +109,42 @@ def extract_findings_statistics(findings: list) -> dict:
resources = set()
findings_count = 0
all_fails_are_muted = True
critical_severity_pass = 0
critical_severity_fail = 0
high_severity_pass = 0
high_severity_fail = 0
medium_severity_pass = 0
medium_severity_fail = 0
low_severity_pass = 0
low_severity_fail = 0
for finding in findings:
# Save the resource_id
resources.add(finding.resource_id)
if finding.status == "PASS":
if finding.check_metadata.Severity == "critical":
critical_severity_pass += 1
if finding.check_metadata.Severity == "high":
high_severity_pass += 1
if finding.check_metadata.Severity == "medium":
medium_severity_pass += 1
if finding.check_metadata.Severity == "low":
low_severity_pass += 1
total_pass += 1
findings_count += 1
if finding.muted is True:
muted_pass += 1
if finding.status == "FAIL":
if finding.check_metadata.Severity == "critical":
critical_severity_fail += 1
if finding.check_metadata.Severity == "high":
high_severity_fail += 1
if finding.check_metadata.Severity == "medium":
medium_severity_fail += 1
if finding.check_metadata.Severity == "low":
low_severity_fail += 1
total_fail += 1
findings_count += 1
if finding.muted is True:
@@ -121,6 +158,14 @@ def extract_findings_statistics(findings: list) -> dict:
stats["total_muted_fail"] = muted_fail
stats["resources_count"] = len(resources)
stats["findings_count"] = findings_count
stats["total_critical_severity_fail"] = critical_severity_fail
stats["total_critical_severity_pass"] = critical_severity_pass
stats["total_high_severity_fail"] = high_severity_fail
stats["total_high_severity_pass"] = high_severity_pass
stats["total_medium_severity_fail"] = medium_severity_fail
stats["total_medium_severity_pass"] = medium_severity_pass
stats["total_low_severity_fail"] = medium_severity_fail
stats["total_low_severity_pass"] = medium_severity_pass
stats["all_fails_are_muted"] = all_fails_are_muted
return stats

View File

@@ -121,6 +121,19 @@ class Slack:
"text": f"\n:white_check_mark: *{stats['total_pass']} Passed findings* ({round(stats['total_pass'] / stats['findings_count'] * 100 , 2)}%)\n",
},
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": (
"*Severities:*\n"
f"• *Critical:* {stats['total_critical_severity_pass']} "
f"• *High:* {stats['total_high_severity_pass']} "
f"• *Medium:* {stats['total_medium_severity_pass']} "
f"• *Low:* {stats['total_low_severity_pass']}"
),
},
},
{
"type": "section",
"text": {
@@ -128,6 +141,19 @@ class Slack:
"text": f"\n:x: *{stats['total_fail']} Failed findings* ({round(stats['total_fail'] / stats['findings_count'] * 100 , 2)}%)\n ",
},
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": (
"*Severities:*\n"
f"• *Critical:* {stats['total_critical_severity_fail']} "
f"• *High:* {stats['total_high_severity_fail']} "
f"• *Medium:* {stats['total_medium_severity_fail']} "
f"• *Low:* {stats['total_low_severity_fail']}"
),
},
},
{
"type": "section",
"text": {

View File

@@ -1,6 +1,8 @@
import datetime
from typing import Generator
from prowler.lib.check.check import execute, update_audit_metadata
from prowler.lib.check.check import execute, import_check, update_audit_metadata
from prowler.lib.check.utils import recover_checks_from_provider
from prowler.lib.logger import logger
from prowler.lib.outputs.finding import Finding
from prowler.providers.common.models import Audit_Metadata
@@ -18,8 +20,9 @@ class Scan:
_service_checks_completed: dict[str, set[str]]
_progress: float = 0.0
_findings: list = []
_duration: int = 0
def __init__(self, provider: Provider, checks_to_execute: list[str]):
def __init__(self, provider: Provider, checks_to_execute: list[str] = None):
"""
Scan is the class that executes the checks and yields the progress and the findings.
@@ -29,11 +32,31 @@ class Scan:
"""
self._provider = provider
# Remove duplicated checks and sort them
self._checks_to_execute = sorted(list(set(checks_to_execute)))
self._checks_to_execute = (
sorted(list(set(checks_to_execute)))
if checks_to_execute
else sorted(
[check[0] for check in recover_checks_from_provider(provider.type)]
)
)
self._number_of_checks_to_execute = len(checks_to_execute)
# TODO This should be done depending on the scan args (future feature)
# Discard threat detection checks
if "cloudtrail_threat_detection_enumeration" in self._checks_to_execute:
self._checks_to_execute.remove("cloudtrail_threat_detection_enumeration")
if (
"cloudtrail_threat_detection_privilege_escalation"
in self._checks_to_execute
):
self._checks_to_execute.remove(
"cloudtrail_threat_detection_privilege_escalation"
)
service_checks_to_execute = get_service_checks_to_execute(checks_to_execute)
self._number_of_checks_to_execute = len(self._checks_to_execute)
service_checks_to_execute = get_service_checks_to_execute(
self._checks_to_execute
)
service_checks_completed = dict()
self._service_checks_to_execute = service_checks_to_execute
@@ -61,6 +84,10 @@ class Scan:
self._number_of_checks_completed / self._number_of_checks_to_execute * 100
)
@property
def duration(self) -> int:
return self._duration
@property
def findings(self) -> list:
return self._findings
@@ -95,17 +122,30 @@ class Scan:
audit_progress=0,
)
start_time = datetime.datetime.now()
for check_name in checks_to_execute:
try:
# Recover service from check name
service = get_service_name_from_check_name(check_name)
try:
# Import check module
check_module_path = f"prowler.providers.{self._provider.type}.services.{service}.{check_name}.{check_name}"
lib = import_check(check_module_path)
# Recover functions from check
check_to_execute = getattr(lib, check_name)
check = check_to_execute()
except ModuleNotFoundError:
logger.error(
f"Check '{check_name}' was not found for the {self._provider.type.upper()} provider"
)
continue
# Execute the check
check_findings = execute(
service,
check_name,
check,
self._provider,
custom_checks_metadata,
output_options=None,
)
# Store findings
@@ -131,12 +171,13 @@ class Scan:
)
findings = [
Finding.generate_output(self._provider, finding)
Finding.generate_output(
self._provider, finding, output_options=None
)
for finding in check_findings
]
yield self.progress, findings
# If check does not exists in the provider or is from another provider
except ModuleNotFoundError:
logger.error(
@@ -146,6 +187,8 @@ class Scan:
logger.error(
f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
# Update the scan duration when all checks are completed
self._duration = int((datetime.datetime.now() - start_time).total_seconds())
except Exception as error:
logger.error(
f"{check_name} - {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"

View File

@@ -133,7 +133,7 @@ def detect_secrets_scan(
{"name": "SoftlayerDetector"},
{"name": "SquareOAuthDetector"},
{"name": "StripeDetector"},
# {"name": "TelegramBotTokenDetector"}, https://github.com/Yelp/detect-secrets/pull/878
{"name": "TelegramBotTokenDetector"},
{"name": "TwilioKeyDetector"},
],
"filters_used": [

View File

@@ -13,13 +13,8 @@ from colorama import Fore, Style
from pytz import utc
from tzlocal import get_localzone
from prowler.config.config import (
aws_services_json_file,
get_default_mute_file_path,
load_and_validate_config_file,
load_and_validate_fixer_config_file,
)
from prowler.lib.check.check import list_modules, recover_checks_from_service
from prowler.config.config import aws_services_json_file, get_default_mute_file_path
from prowler.lib.check.utils import list_modules, recover_checks_from_service
from prowler.lib.logger import logger
from prowler.lib.utils.utils import open_file, parse_json_file, print_boxes
from prowler.providers.aws.config import (
@@ -57,7 +52,6 @@ from prowler.providers.aws.models import (
AWSIdentityInfo,
AWSMFAInfo,
AWSOrganizationsInfo,
AWSOutputOptions,
AWSSession,
)
from prowler.providers.common.models import Audit_Metadata, Connection
@@ -73,7 +67,6 @@ class AwsProvider(Provider):
_audit_config: dict
_scan_unused_services: bool = False
_enabled_regions: set = set()
_output_options: AWSOutputOptions
# TODO: this is not optional, enforce for all providers
audit_metadata: Audit_Metadata
@@ -91,8 +84,8 @@ class AwsProvider(Provider):
scan_unused_services: bool = None,
resource_tags: list[str] = [],
resource_arn: list[str] = [],
config_file: str = None,
fixer_config: str = None,
audit_config: dict = {},
fixer_config: dict = {},
):
"""
Initializes the AWS provider.
@@ -110,8 +103,8 @@ class AwsProvider(Provider):
- scan_unused_services: A boolean indicating whether to scan unused services.
- resource_tags: A list of tags to filter the resources to audit.
- resource_arn: A list of ARNs of the resources to audit.
- config_file: The path to the configuration file.
- fixer_config: The path to the fixer configuration
- audit_config: The audit configuration.
- fixer_config: The fixer configuration.
Raises:
- ArgumentTypeError: If the input MFA ARN is invalid.
@@ -270,16 +263,12 @@ class AwsProvider(Provider):
# Set ignore unused services
self._scan_unused_services = scan_unused_services
# TODO: move this to the providers, pending for AWS, GCP, AZURE and K8s
# Audit Config
self._audit_config = {}
if config_file:
self._audit_config = load_and_validate_config_file(self._type, config_file)
self._fixer_config = {}
if fixer_config:
self._fixer_config = load_and_validate_fixer_config_file(
self._type, fixer_config
)
self._audit_config = audit_config
# Fixer Config
self._fixer_config = fixer_config
Provider.set_global_provider(self)
@property
def identity(self):
@@ -313,17 +302,6 @@ class AwsProvider(Provider):
def fixer_config(self):
return self._fixer_config
@property
def output_options(self):
return self._output_options
@output_options.setter
def output_options(self, options: tuple):
arguments, bulk_checks_metadata = options
self._output_options = AWSOutputOptions(
arguments, bulk_checks_metadata, self._identity
)
@property
def mutelist(self) -> AWSMutelist:
"""

View File

@@ -174,6 +174,8 @@
"regions": {
"aws": [
"ap-south-1",
"ap-southeast-2",
"eu-west-2",
"us-east-1",
"us-east-2",
"us-west-2"
@@ -221,6 +223,7 @@
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"sa-east-1",
@@ -617,6 +620,7 @@
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"ca-west-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
@@ -667,8 +671,10 @@
"ap-southeast-3",
"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",
@@ -1257,6 +1263,7 @@
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
@@ -1267,6 +1274,7 @@
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
],
"aws-cn": [],
@@ -1871,6 +1879,7 @@
"eu-central-1",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -2219,27 +2228,6 @@
]
}
},
"codestar": {
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"codestar-connections": {
"regions": {
"aws": [
@@ -3187,6 +3175,7 @@
"docdb": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
@@ -3197,6 +3186,7 @@
"ca-central-1",
"eu-central-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -3586,6 +3576,7 @@
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ca-central-1",
"ca-west-1",
"eu-central-1",
@@ -4961,15 +4952,6 @@
]
}
},
"honeycode": {
"regions": {
"aws": [
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"iam": {
"regions": {
"aws": [
@@ -6839,6 +6821,7 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-4",
@@ -6848,6 +6831,7 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"sa-east-1",
"us-east-1",
"us-east-2",
@@ -6944,6 +6928,7 @@
"eu-central-1",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -7222,9 +7207,11 @@
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -7529,11 +7516,13 @@
"regions": {
"aws": [
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -7544,6 +7533,7 @@
],
"aws-cn": [],
"aws-us-gov": [
"us-gov-east-1",
"us-gov-west-1"
]
}
@@ -7705,6 +7695,7 @@
"eu-central-1",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
@@ -8450,12 +8441,22 @@
"redshift-serverless": {
"regions": {
"aws": [
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"il-central-1",
"me-central-1",
"sa-east-1",
"us-east-1",
@@ -9466,6 +9467,7 @@
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ap-southeast-5",
"ca-central-1",
"ca-west-1",
"eu-central-1",
@@ -10149,16 +10151,22 @@
"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",
"il-central-1",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
@@ -10558,12 +10566,16 @@
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"us-east-1",
"us-east-2",
"us-west-2"
@@ -10965,8 +10977,10 @@
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
@@ -10977,6 +10991,7 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",

View File

@@ -1,157 +0,0 @@
def is_condition_block_restrictive(
condition_statement: dict,
source_account: str,
is_cross_account_allowed=False,
):
"""
is_condition_block_restrictive parses the IAM Condition policy block and, by default, returns True if the source_account passed as argument is within, False if not.
If argument is_cross_account_allowed is True it tests if the Condition block includes any of the operators allowlisted returning True if does, False if not.
@param condition_statement: dict with an IAM Condition block, e.g.:
{
"StringLike": {
"AWS:SourceAccount": 111122223333
}
}
@param source_account: str with a 12-digit AWS Account number, e.g.: 111122223333
@param is_cross_account_allowed: bool to allow cross-account access, e.g.: True
"""
is_condition_valid = False
# The conditions must be defined in lowercase since the context key names are not case-sensitive.
# For example, including the aws:SourceAccount context key is equivalent to testing for AWS:SourceAccount
# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html
valid_condition_options = {
"StringEquals": [
"aws:sourceaccount",
"aws:sourceowner",
"s3:resourceaccount",
"aws:principalaccount",
"aws:resourceaccount",
"aws:sourcearn",
"aws:sourcevpc",
"aws:sourcevpce",
],
"StringLike": [
"aws:sourceaccount",
"aws:sourceowner",
"aws:sourcearn",
"aws:principalarn",
"aws:resourceaccount",
"aws:principalaccount",
"aws:sourcevpc",
"aws:sourcevpce",
],
"ArnLike": ["aws:sourcearn", "aws:principalarn"],
"ArnEquals": ["aws:sourcearn", "aws:principalarn"],
}
for condition_operator, condition_operator_key in valid_condition_options.items():
if condition_operator in condition_statement:
for value in condition_operator_key:
# We need to transform the condition_statement into lowercase
condition_statement[condition_operator] = {
k.lower(): v
for k, v in condition_statement[condition_operator].items()
}
if value in condition_statement[condition_operator]:
# values are a list
if isinstance(
condition_statement[condition_operator][value],
list,
):
is_condition_key_restrictive = True
# if cross account is not allowed check for each condition block looking for accounts
# different than default
if not is_cross_account_allowed:
# if there is an arn/account without the source account -> we do not consider it safe
# here by default we assume is true and look for false entries
for item in condition_statement[condition_operator][value]:
if source_account not in item:
is_condition_key_restrictive = False
break
if is_condition_key_restrictive:
is_condition_valid = True
# value is a string
elif isinstance(
condition_statement[condition_operator][value],
str,
):
if is_cross_account_allowed:
is_condition_valid = True
else:
if (
source_account
in condition_statement[condition_operator][value]
):
is_condition_valid = True
return is_condition_valid
def is_condition_block_restrictive_organization(
condition_statement: dict,
):
"""
is_condition_block_restrictive_organization parses the IAM Condition policy block and returns True if the condition_statement is restrictive for the organization, False if not.
@param condition_statement: dict with an IAM Condition block, e.g.:
{
"StringLike": {
"AWS:PrincipalOrgID": "o-111122223333"
}
}
"""
is_condition_valid = False
# The conditions must be defined in lowercase since the context key names are not case-sensitive.
# For example, including the aws:PrincipalOrgID context key is equivalent to testing for AWS:PrincipalOrgID
# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html
valid_condition_options = {
"StringEquals": [
"aws:principalorgid",
],
"StringLike": [
"aws:principalorgid",
],
}
for condition_operator, condition_operator_key in valid_condition_options.items():
if condition_operator in condition_statement:
for value in condition_operator_key:
# We need to transform the condition_statement into lowercase
condition_statement[condition_operator] = {
k.lower(): v
for k, v in condition_statement[condition_operator].items()
}
if value in condition_statement[condition_operator]:
# values are a list
if isinstance(
condition_statement[condition_operator][value],
list,
):
is_condition_valid = True
for item in condition_statement[condition_operator][value]:
if item == "*":
is_condition_valid = False
break
# value is a string
elif isinstance(
condition_statement[condition_operator][value],
str,
):
if "*" not in condition_statement[condition_operator][value]:
is_condition_valid = True
return is_condition_valid

View File

@@ -12,7 +12,7 @@
"SubServiceName": "rest_api",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsApiGatewayStage",
"ResourceType": "AwsApiGatewayRestApi",
"Description": "Check if API Gateway Stage has client certificate enabled to access your backend endpoint.",
"Risk": "Possible man in the middle attacks and other similar risks.",
"RelatedUrl": "",

View File

@@ -12,7 +12,7 @@
"SubServiceName": "rest_api",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsApiGatewayStage",
"ResourceType": "AwsApiGatewayRestApi",
"Description": "Check if API Gateway Stage has logging enabled.",
"Risk": "If not enabled, monitoring of service use is not possible. Real-time monitoring of API calls can be achieved by directing CloudTrail Logs to CloudWatch Logs and establishing corresponding metric filters and alarms.",
"RelatedUrl": "",

View File

@@ -15,7 +15,7 @@ class apigateway_restapi_public(Check):
report.resource_tags = rest_api.tags
if rest_api.public_endpoint:
report.status = "FAIL"
report.status_extended = f"API Gateway {rest_api.name} ID {rest_api.id} is internet accesible."
report.status_extended = f"API Gateway {rest_api.name} ID {rest_api.id} is internet accessible."
else:
report.status = "PASS"
report.status_extended = (

View File

@@ -12,7 +12,7 @@
"SubServiceName": "rest_api",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsApiGatewayStage",
"ResourceType": "AwsApiGatewayRestApi",
"Description": "Check if API Gateway Stage has a WAF ACL attached.",
"Risk": "Potential attacks and / or abuse of service, more even for even for internet reachable services.",
"RelatedUrl": "",

View File

@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "AppStream",
"ResourceType": "Other",
"Description": "Ensure default Internet Access from your Amazon AppStream fleet streaming instances should remain unchecked.",
"Risk": "Default Internet Access from your fleet streaming instances should be controlled using a NAT gateway in the VPC.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",

View File

@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "AppStream",
"ResourceType": "Other",
"Description": "Ensure user maximum session duration is no longer than 10 hours.",
"Risk": "Having a session duration lasting longer than 10 hours should not be necessary and if running for any malicious reasons provides a greater time for usage than should be allowed.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",

View File

@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "AppStream",
"ResourceType": "Other",
"Description": "Ensure session disconnect timeout is set to 5 minutes or less",
"Risk": "Disconnect timeout in minutes, is the amount of of time that a streaming session remains active after users disconnect.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",

View File

@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:appstream:region:account-id:fleet/resource-id",
"Severity": "medium",
"ResourceType": "AppStream",
"ResourceType": "Other",
"Description": "Ensure session idle disconnect timeout is set to 10 minutes or less.",
"Risk": "Idle disconnect timeout in minutes is the amount of time that users can be inactive before they are disconnected from their streaming session and the Disconnect timeout in minutes time begins.",
"RelatedUrl": "https://docs.aws.amazon.com/appstream2/latest/developerguide/set-up-stacks-fleets.html",

View File

@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:athena:region:account-id:workgroup/resource-id",
"Severity": "medium",
"ResourceType": "WorkGroup",
"ResourceType": "AwsAthenaWorkGroup",
"Description": "Ensure that encryption at rest is enabled for Amazon Athena query results stored in Amazon S3 in order to secure data and meet compliance requirements for data-at-rest encryption.",
"Risk": "If not enabled sensitive information at rest is not protected.",
"RelatedUrl": "https://docs.aws.amazon.com/athena/latest/ug/encryption.html",

View File

@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:athena:region:account-id:workgroup/resource-id",
"Severity": "medium",
"ResourceType": "WorkGroup",
"ResourceType": "AwsAthenaWorkGroup",
"Description": "Ensure that workgroup configuration is enforced so it cannot be overriden by client-side settings.",
"Risk": "If workgroup configuration is not enforced security settings like encryption can be overriden by client-side settings.",
"RelatedUrl": "https://docs.aws.amazon.com/athena/latest/ug/workgroups-settings-override.html",

View File

@@ -0,0 +1,6 @@
from prowler.providers.aws.services.autoscaling.autoscaling_service import (
ApplicationAutoScaling,
)
from prowler.providers.common.provider import Provider
applicationautoscaling_client = ApplicationAutoScaling(Provider.get_global_provider())

View File

@@ -1,7 +1,7 @@
{
"Provider": "aws",
"CheckID": "autoscaling_find_secrets_ec2_launch_configuration",
"CheckTitle": "Find secrets in EC2 Auto Scaling Launch Configuration",
"CheckTitle": "[DEPRECATED] Find secrets in EC2 Auto Scaling Launch Configuration",
"CheckType": [
"IAM"
],
@@ -9,8 +9,8 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:autoscaling:region:account-id:autoScalingGroupName/resource-name",
"Severity": "critical",
"ResourceType": "Other",
"Description": "Find secrets in EC2 Auto Scaling Launch Configuration",
"ResourceType": "AwsAutoScalingLaunchConfiguration",
"Description": "[DEPRECATED] Find secrets in EC2 Auto Scaling Launch Configuration",
"Risk": "The use of a hard-coded password increases the possibility of password guessing. If hard-coded passwords are used, it is possible that malicious users gain access through the account in question.",
"RelatedUrl": "",
"Remediation": {

View File

@@ -7,7 +7,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:autoscaling:region:account-id:autoScalingGroupName/resource-name",
"Severity": "medium",
"ResourceType": "Other",
"ResourceType": "AwsAutoScalingAutoScalingGroup",
"Description": "EC2 Auto Scaling Group should use multiple Availability Zones",
"Risk": "In case of a failure in a single Availability Zone, the Auto Scaling Group will not be able to launch new instances to replace the failed ones.",
"RelatedUrl": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-add-availability-zone.html",

View File

@@ -5,7 +5,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
################## AutoScaling
class AutoScaling(AWSService):
def __init__(self, provider):
# Call AWSService's __init__
@@ -74,6 +73,49 @@ class AutoScaling(AWSService):
)
# Global list for service namespaces needed for Describe Scalable Targets
SERVICE_NAMESPACES = ["dynamodb"]
class ApplicationAutoScaling(AWSService):
def __init__(self, provider):
super().__init__("application-autoscaling", provider)
self.scalable_targets = []
self.__threading_call__(self._describe_scalable_targets)
def _describe_scalable_targets(self, regional_client):
logger.info("ApplicationAutoScaling - Describing Scalable Targets...")
try:
describe_scalable_targets_paginator = regional_client.get_paginator(
"describe_scalable_targets"
)
for service_namespace in SERVICE_NAMESPACES:
logger.info(f"Processing ServiceNamespace: {service_namespace}")
for page in describe_scalable_targets_paginator.paginate(
ServiceNamespace=service_namespace
):
for target in page.get("ScalableTargets", []):
if not self.audit_resources or (
is_resource_filtered(
target["ScalableTargetARN"],
self.audit_resources,
)
):
self.scalable_targets.append(
ScalableTarget(
arn=target.get("ScalableTargetARN", ""),
resource_id=target.get("ResourceId"),
service_namespace=target.get("ServiceNamespace"),
scalable_dimension=target.get("ScalableDimension"),
region=regional_client.region,
)
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class LaunchConfiguration(BaseModel):
arn: str
name: str
@@ -88,3 +130,11 @@ class Group(BaseModel):
region: str
availability_zones: list
tags: list = []
class ScalableTarget(BaseModel):
arn: str
resource_id: str
service_namespace: str
scalable_dimension: str
region: str

View File

@@ -13,14 +13,20 @@ class awslambda_function_inside_vpc(Check):
report.resource_id = function.name
report.resource_arn = function_arn
report.resource_tags = function.tags
report.status = "FAIL"
report.status = "PASS"
report.status_extended = (
f"Lambda function {function.name} is not inside a VPC"
f"Lambda function {function.name} is inside of VPC {function.vpc_id}"
)
if function.vpc_id:
report.status = "PASS"
report.status_extended = f"Lambda function {function.name} is inside of VPC {function.vpc_id}"
if not function.vpc_id:
awslambda_client.set_failed_check(
self.__class__.__name__,
function_arn,
)
report.status = "FAIL"
report.status_extended = (
f"Lambda function {function.name} is not inside a VPC"
)
findings.append(report)

View File

@@ -28,7 +28,8 @@ class awslambda_function_invoke_api_operations_cloudtrail_logging_enabled(Check)
for resource in data_event.event_selector["DataResources"]:
if resource["Type"] == "AWS::Lambda::Function" and (
function.arn in resource["Values"]
or "arn:aws:lambda" in resource["Values"]
or f"arn:{awslambda_client.audited_partition}:lambda"
in resource["Values"]
):
lambda_recorded_cloudtrail = True
break

View File

@@ -1,5 +1,6 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.awslambda.awslambda_client import awslambda_client
from prowler.providers.aws.services.iam.lib.policy import is_policy_public
class awslambda_function_not_publicly_accessible(Check):
@@ -14,37 +15,11 @@ class awslambda_function_not_publicly_accessible(Check):
report.status = "PASS"
report.status_extended = f"Lambda function {function.name} has a policy resource-based policy not public."
public_access = False
if function.policy:
for statement in function.policy["Statement"]:
# Only check allow statements
if statement["Effect"] == "Allow" and (
"*" in statement["Principal"]
or (
isinstance(statement["Principal"], dict)
and (
"*" in statement["Principal"].get("AWS", "")
or "*"
in statement["Principal"].get("CanonicalUser", "")
or ( # Check if function can be invoked by other AWS services
(
".amazonaws.com"
in statement["Principal"].get("Service", "")
)
and (
"*" in statement.get("Action", "")
or "InvokeFunction"
in statement.get("Action", "")
)
)
)
)
):
public_access = True
break
if public_access:
if is_policy_public(
function.policy,
awslambda_client.audited_account,
is_cross_account_allowed=True,
):
report.status = "FAIL"
report.status_extended = f"Lambda function {function.name} has a policy resource-based policy with public access."

View File

@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "awslambda_function_vpc_multi_az",
"CheckTitle": "Check if AWS Lambda Function VPC is deployed Across Multiple Availability Zones",
"CheckType": [],
"ServiceName": "lambda",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:lambda:region:account-id:function/function-name",
"Severity": "medium",
"ResourceType": "AwsLambdaFunction",
"Description": "This control checks whether an AWS Lambda function connected to a VPC operates in at least the specified number of Availability Zones (AZs). A failure occurs if the function does not operate in the required number of AZs, which by default is two.",
"Risk": "A Lambda function not deployed across multiple AZs increases the risk of a single point of failure, which can result in a complete disruption of the function's operations if an AZ becomes unavailable.",
"RelatedUrl": "https://docs.aws.amazon.com/lambda/latest/operatorguide/networking-vpc.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/lambda-controls.html#lambda-5",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure that your AWS Lambda functions connected to a VPC are distributed across multiple Availability Zones (AZs) to enhance availability and resilience.",
"Url": "https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,48 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.awslambda.awslambda_client import awslambda_client
from prowler.providers.aws.services.awslambda.awslambda_function_inside_vpc.awslambda_function_inside_vpc import (
awslambda_function_inside_vpc,
)
from prowler.providers.aws.services.vpc.vpc_client import vpc_client
class awslambda_function_vpc_multi_az(Check):
def execute(self) -> list[Check_Report_AWS]:
findings = []
LAMBDA_MIN_AZS = awslambda_client.audit_config.get("lambda_min_azs", 2)
for function_arn, function in awslambda_client.functions.items():
# only proceed if check "awslambda_function_inside_vpc" did not run or did not FAIL to avoid to report that the function is not inside a VPC twice
if not awslambda_client.is_failed_check(
awslambda_function_inside_vpc.__name__,
function_arn,
):
report = Check_Report_AWS(self.metadata())
report.region = function.region
report.resource_id = function.name
report.resource_arn = function_arn
report.resource_tags = function.tags
report.status = "FAIL"
report.status_extended = (
f"Lambda function {function.name} is not inside a VPC."
)
if function.vpc_id:
function_availability_zones = {
getattr(
vpc_client.vpc_subnets.get(subnet_id),
"availability_zone",
None,
)
for subnet_id in function.subnet_ids
if subnet_id in vpc_client.vpc_subnets
}
if len(function_availability_zones) >= LAMBDA_MIN_AZS:
report.status = "PASS"
report.status_extended = f"Lambda function {function.name} is inside of VPC {function.vpc_id} that spans in at least {LAMBDA_MIN_AZS} AZs: {', '.join(function_availability_zones)}."
else:
report.status_extended = f"Lambda function {function.name} is inside of VPC {function.vpc_id} that spans only in {len(function_availability_zones)} AZs: {', '.join(function_availability_zones)}. Must span in at least {LAMBDA_MIN_AZS} AZs."
findings.append(report)
return findings

View File

@@ -44,6 +44,7 @@ class Lambda(AWSService):
arn=lambda_arn,
security_groups=vpc_config.get("SecurityGroupIds", []),
vpc_id=vpc_config.get("VpcId"),
subnet_ids=set(vpc_config.get("SubnetIds", [])),
region=regional_client.region,
)
if "Runtime" in function:
@@ -202,4 +203,5 @@ class Function(BaseModel):
code: LambdaCode = None
url_config: URLConfig = None
vpc_id: Optional[str]
subnet_ids: Optional[set]
tags: Optional[list] = []

View File

@@ -12,6 +12,7 @@ class backup_plans_exist(Check):
report.resource_arn = backup_client.backup_plans[0].arn
report.resource_id = backup_client.backup_plans[0].name
report.region = backup_client.backup_plans[0].region
report.resource_tags = backup_client.backup_plans[0].tags
findings.append(report)
elif backup_client.backup_vaults:
report = Check_Report_AWS(self.metadata())
@@ -20,5 +21,6 @@ class backup_plans_exist(Check):
report.resource_arn = backup_client.backup_plan_arn_template
report.resource_id = backup_client.audited_account
report.region = backup_client.region
report.resource_tags = []
findings.append(report)
return findings

View File

@@ -11,7 +11,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:backup-report-plan:backup-report-plan-id",
"Severity": "low",
"ResourceType": "Other",
"ResourceType": "AwsBackupBackupPlan",
"Description": "This check ensures that there is at least one backup report plan in place.",
"Risk": "Without a backup report plan, an organization may lack visibility into the success or failure of backup operations.",
"RelatedUrl": "https://docs.aws.amazon.com/aws-backup/latest/devguide/create-report-plan-console.html",

View File

@@ -9,7 +9,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
################## Backup
class Backup(AWSService):
def __init__(self, provider):
# Call AWSService's __init__
@@ -19,10 +18,14 @@ class Backup(AWSService):
self.backup_vault_arn_template = f"arn:{self.audited_partition}:backup:{self.region}:{self.audited_account}:backup-vault"
self.backup_vaults = []
self.__threading_call__(self._list_backup_vaults)
self.__threading_call__(self._list_tags, self.backup_vaults)
self.backup_plans = []
self.__threading_call__(self._list_backup_plans)
self.__threading_call__(self._list_tags, self.backup_plans)
self.backup_report_plans = []
self.__threading_call__(self._list_backup_report_plans)
self.protected_resources = []
self.__threading_call__(self._list_backup_selections)
def _list_backup_vaults(self, regional_client):
logger.info("Backup - Listing Backup Vaults...")
@@ -138,6 +141,43 @@ class Backup(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _list_backup_selections(self, regional_client):
logger.info("Backup - Listing Backup Selections...")
try:
for backup_plan in self.backup_plans:
paginator = regional_client.get_paginator("list_backup_selections")
for page in paginator.paginate(BackupPlanId=backup_plan.id):
for selection in page.get("BackupSelectionsList", []):
selection_id = selection.get("SelectionId")
if selection_id:
backup_selection = regional_client.get_backup_selection(
BackupPlanId=backup_plan.id, SelectionId=selection_id
)["BackupSelection"]
self.protected_resources.extend(
backup_selection.get("Resources", [])
)
except ClientError as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _list_tags(self, resource):
try:
tags = self.regional_clients[resource.region].list_tags(
ResourceArn=resource.arn
)["Tags"]
resource.tags = [tags] if tags else []
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
class BackupVault(BaseModel):
arn: str
@@ -148,6 +188,7 @@ class BackupVault(BaseModel):
locked: bool
min_retention_days: int = None
max_retention_days: int = None
tags: Optional[list]
class BackupPlan(BaseModel):
@@ -158,6 +199,7 @@ class BackupPlan(BaseModel):
version_id: str
last_execution_date: Optional[datetime]
advanced_settings: list
tags: Optional[list]
class BackupReportPlan(BaseModel):

View File

@@ -16,6 +16,7 @@ class backup_vaults_encrypted(Check):
report.resource_arn = backup_vault.arn
report.resource_id = backup_vault.name
report.region = backup_vault.region
report.resource_tags = backup_vault.tags
# if it is encrypted we only change the status and the status extended
if backup_vault.encryption:
report.status = "PASS"

View File

@@ -12,12 +12,14 @@ class backup_vaults_exist(Check):
report.resource_arn = backup_client.backup_vault_arn_template
report.resource_id = backup_client.audited_account
report.region = backup_client.region
report.resource_tags = []
if backup_client.backup_vaults:
report.status = "PASS"
report.status_extended = f"At least one backup vault exists: {backup_client.backup_vaults[0].name}."
report.resource_arn = backup_client.backup_vaults[0].arn
report.resource_id = backup_client.backup_vaults[0].name
report.region = backup_client.backup_vaults[0].region
report.resource_tags = backup_client.backup_vaults[0].tags
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_custom_ssl_certificate",
"CheckTitle": "CloudFront distributions should use custom SSL/TLS certificates.",
"CheckType": [],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudFrontDistribution",
"Description": "Ensure that your Amazon CloudFront distributions are configured to use a custom SSL/TLS certificate instead of the default one.",
"Risk": "Using the default SSL/TLS certificate provided by CloudFront can limit your ability to use custom domain names and may not align with your organization's security policies or branding requirements.",
"RelatedUrl": "https://aws.amazon.com/what-is/ssl-certificate/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "https://docs.prowler.com/checks/aws/networking-policies/ensure-aws-cloudfront-distribution-uses-custom-ssl-certificate/",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-7",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-distro-custom-tls.html"
},
"Recommendation": {
"Text": "Configure your CloudFront distributions to use a custom SSL/TLS certificate to enable secure access via your own domain names and meet specific security and branding needs. This allows for more control over encryption and authentication settings.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#CreatingCNAME"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
class cloudfront_distributions_custom_ssl_certificate(Check):
def execute(self):
findings = []
for distribution in cloudfront_client.distributions.values():
report = Check_Report_AWS(self.metadata())
report.region = distribution.region
report.resource_arn = distribution.arn
report.resource_id = distribution.id
report.resource_tags = distribution.tags
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} is using a custom SSL/TLS certificate."
if distribution.default_certificate:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} is using the default SSL/TLS certificate."
findings.append(report)
return findings

View File

@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_default_root_object",
"CheckTitle": "Check if CloudFront distributions have a default root object.",
"CheckType": [],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id",
"Severity": "high",
"ResourceType": "AwsCloudFrontDistribution",
"Description": "Check if CloudFront distributions have a default root object.",
"Risk": "Without a default root object, requests to the root URL may result in an error or expose unintended content, leading to potential security risks and a poor user experience.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html#DefaultRootObjectHow",
"Remediation": {
"Code": {
"CLI": "aws cloudfront update-distribution --id <distribution-id> --default-root-object <new-root-object>",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-1",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-default-object.html"
},
"Recommendation": {
"Text": "Configure a default root object for your CloudFront distribution to ensure that a specific file (such as index.html) is returned when users access the root URL. This improves user experience and ensures that sensitive content isn't accidentally exposed.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html#DefaultRootObjectHowToDefine"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,26 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
class cloudfront_distributions_default_root_object(Check):
def execute(self):
findings = []
for distribution in cloudfront_client.distributions.values():
report = Check_Report_AWS(self.metadata())
report.region = distribution.region
report.resource_arn = distribution.arn
report.resource_id = distribution.id
report.resource_tags = distribution.tags
if distribution.default_root_object:
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} does have a default root object ({distribution.default_root_object}) configured."
else:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} does not have a default root object configured."
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_https_sni_enabled",
"CheckTitle": "Check if CloudFront distributions are using SNI to serve HTTPS requests.",
"CheckType": [],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id",
"Severity": "low",
"ResourceType": "AwsCloudFrontDistribution",
"Description": "Check if CloudFront distributions are using SNI to serve HTTPS requests.",
"Risk": "If SNI is not used, CloudFront will allocate a dedicated IP address for each SSL certificate, leading to higher costs and inefficient IP address utilization. This could also complicate scaling and managing multiple distributions, especially if your domain requires multiple SSL certificates.",
"RelatedUrl": "https://www.cloudflare.com/es-es/learning/ssl/what-is-sni/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-8",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-sni.html"
},
"Recommendation": {
"Text": "Ensure that your CloudFront distributions are configured to use Server Name Indication (SNI) when serving HTTPS requests with custom SSL/TLS certificates. This is the recommended approach for reducing costs and optimizing IP address usage.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-https-dedicated-ip-or-sni.html#cnames-https-sni"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,30 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
from prowler.providers.aws.services.cloudfront.cloudfront_service import (
SSLSupportMethod,
)
class cloudfront_distributions_https_sni_enabled(Check):
def execute(self):
findings = []
for distribution in cloudfront_client.distributions.values():
if distribution.certificate:
report = Check_Report_AWS(self.metadata())
report.region = distribution.region
report.resource_arn = distribution.arn
report.resource_id = distribution.id
report.resource_tags = distribution.tags
if distribution.ssl_support_method == SSLSupportMethod.sni_only:
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} is serving HTTPS requests using SNI."
else:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} is not serving HTTPS requests using SNI."
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_multiple_origin_failover_configured",
"CheckTitle": "Check if CloudFront distributions have origin failover enabled.",
"CheckType": [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"NIST 800-53 Controls"
],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id",
"Severity": "low",
"ResourceType": "AWSCloudFrontDistribution",
"Description": "Check if CloudFront distributions have origin failover enabled.",
"Risk": "Without origin failover, if the primary origin becomes unavailable, your CloudFront distribution may experience downtime, leading to potential service interruptions and a poor user experience.",
"RelatedUrl": "https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_OriginGroup.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-4",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/origin-failover-enabled.html"
},
"Recommendation": {
"Text": "Configure origin failover in your CloudFront distribution by setting up an origin group with at least two origins to enhance availability and ensure traffic is redirected if the primary origin fails.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/high_availability_origin_failover.html#concept_origin_groups.creating"
}
},
"Categories": [
"redundancy"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
class cloudfront_distributions_multiple_origin_failover_configured(Check):
def execute(self):
findings = []
for distribution in cloudfront_client.distributions.values():
report = Check_Report_AWS(self.metadata())
report.region = distribution.region
report.resource_arn = distribution.arn
report.resource_id = distribution.id
report.resource_tags = distribution.tags
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} does not have an origin group configured with at least 2 origins."
if distribution.origin_failover:
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} has an origin group with at least 2 origins configured."
findings.append(report)
return findings

View File

@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_origin_traffic_encrypted",
"CheckTitle": "Check if CloudFront distributions encrypt traffic to custom origins.",
"CheckType": [],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id",
"Severity": "medium",
"ResourceType": "AWSCloudFrontDistribution",
"Description": "Check if CloudFront distributions encrypt traffic to custom origins.",
"Risk": "Allowing unencrypted HTTP traffic between CloudFront and custom origins can expose data to potential eavesdropping and manipulation, compromising data security and integrity.",
"RelatedUrl": "https://docs.aws.amazon.com/whitepapers/latest/secure-content-delivery-amazon-cloudfront/custom-origin-with-cloudfront.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-9",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-traffic-to-origin-unencrypted.html"
},
"Recommendation": {
"Text": "Configure your CloudFront distributions to require HTTPS (TLS) for traffic to custom origins, ensuring all data transmitted between CloudFront and the origin is encrypted and protected from unauthorized access.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-https-cloudfront-to-custom-origin.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,36 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
class cloudfront_distributions_origin_traffic_encrypted(Check):
def execute(self):
findings = []
for distribution in cloudfront_client.distributions.values():
report = Check_Report_AWS(self.metadata())
report.region = distribution.region
report.resource_arn = distribution.arn
report.resource_id = distribution.id
report.resource_tags = distribution.tags
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} does encrypt traffic to custom origins."
unencrypted_origins = []
for origin in distribution.origins:
if (
origin.origin_protocol_policy == ""
or origin.origin_protocol_policy == "http-only"
) or (
origin.origin_protocol_policy == "match-viewer"
and distribution.viewer_protocol_policy == "allow-all"
):
unencrypted_origins.append(origin.id)
if unencrypted_origins:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} does not encrypt traffic to custom origins {', '.join(unencrypted_origins)}."
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_s3_origin_access_control",
"CheckTitle": "Check if CloudFront distributions with S3 origin use OAC.",
"CheckType": [
"Data Exposure"
],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id",
"Severity": "medium",
"ResourceType": "AWSCloudFrontDistribution",
"Description": "Check if CloudFront distributions use origin access control.",
"Risk": "Without OAC, your S3 bucket could be accessed directly, bypassing CloudFront, which could expose your content to unauthorized access. Additionally, relying on Origin Access Identity (OAI) may limit functionality and security features, making your distribution less secure and more difficult to manage.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#migrate-from-oai-to-oac",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "https://docs.prowler.com/checks/aws/iam-policies/ensure-aws-cloudfromt-distribution-with-s3-have-origin-access-set-to-enabled/",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-13",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/s3-origin.html"
},
"Recommendation": {
"Text": "Configure Origin Access Control (OAC) for CloudFront distributions that use an Amazon S3 origin. This will ensure that the content in your S3 bucket is accessible only through the specified CloudFront distribution, enhancing security by preventing direct access to the bucket.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,35 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
class cloudfront_distributions_s3_origin_access_control(Check):
def execute(self):
findings = []
for distribution in cloudfront_client.distributions.values():
report = Check_Report_AWS(self.metadata())
report.region = distribution.region
report.resource_arn = distribution.arn
report.resource_id = distribution.id
report.resource_tags = distribution.tags
if any(origin.s3_origin_config for origin in distribution.origins):
s3_buckets_with_no_oac = []
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} is using origin access control (OAC) for S3 origins."
for origin in distribution.origins:
if (
origin.s3_origin_config != {}
and origin.origin_access_control == ""
):
s3_buckets_with_no_oac.append(origin.id)
if s3_buckets_with_no_oac:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} is not using origin access control (OAC) in S3 origins {', '.join(s3_buckets_with_no_oac)}."
findings.append(report)
return findings

View File

@@ -0,0 +1,34 @@
{
"Provider": "aws",
"CheckID": "cloudfront_distributions_s3_origin_non_existent_bucket",
"CheckTitle": "CloudFront distributions should not point to non-existent S3 origins.",
"CheckType": [
"Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls"
],
"ServiceName": "cloudfront",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudfront:region:account-id:distribution/resource-id",
"Severity": "high",
"ResourceType": "AwsCloudFrontDistribution",
"Description": "This control checks whether Amazon CloudFront distributions are pointing to non-existent Amazon S3 origins. The control fails if the origin is configured to point to a non-existent bucket.",
"Risk": "Pointing a CloudFront distribution to a non-existent S3 bucket can allow malicious actors to create the bucket and potentially serve unauthorized content through your distribution, leading to security and integrity issues.",
"RelatedUrl": "https://docs.aws.amazon.com/whitepapers/latest/secure-content-delivery-amazon-cloudfront/s3-origin-with-cloudfront.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/cloudfront-controls.html#cloudfront-12",
"Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/CloudFront/cloudfront-existing-s3-bucket.html"
},
"Recommendation": {
"Text": "Verify that all CloudFront distributions are configured to point to valid, existing S3 buckets. Update the origin settings as needed to ensure that your distributions are linked to appropriate and secure origins.",
"Url": "https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/HowToUpdateDistribution.html"
}
},
"Categories": [
"trustboundaries"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,32 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudfront.cloudfront_client import (
cloudfront_client,
)
from prowler.providers.aws.services.s3.s3_client import s3_client
class cloudfront_distributions_s3_origin_non_existent_bucket(Check):
def execute(self):
findings = []
for distribution in cloudfront_client.distributions.values():
report = Check_Report_AWS(self.metadata())
report.region = distribution.region
report.resource_arn = distribution.arn
report.resource_id = distribution.id
report.resource_tags = distribution.tags
report.status = "PASS"
report.status_extended = f"CloudFront Distribution {distribution.id} does not have non-existent S3 buckets as origins."
non_existent_buckets = []
for origin in distribution.origins:
bucket_name = origin.domain_name.split(".")[0]
if not s3_client._head_bucket(bucket_name):
non_existent_buckets.append(bucket_name)
if non_existent_buckets:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} has non-existent S3 buckets as origins: {','.join(non_existent_buckets)}."
findings.append(report)
return findings

View File

@@ -21,10 +21,8 @@ class cloudfront_distributions_using_deprecated_ssl_protocols(Check):
bad_ssl_protocol = False
for origin in distribution.origins:
if "CustomOriginConfig" in origin:
for ssl_protocol in origin["CustomOriginConfig"][
"OriginSslProtocols"
]["Items"]:
if origin.origin_ssl_protocols:
for ssl_protocol in origin.origin_ssl_protocols:
if ssl_protocol in (
OriginsSSLProtocols.SSLv3.value,
OriginsSSLProtocols.TLSv1.value,
@@ -32,6 +30,7 @@ class cloudfront_distributions_using_deprecated_ssl_protocols(Check):
):
bad_ssl_protocol = True
break
if bad_ssl_protocol:
report.status = "FAIL"
report.status_extended = f"CloudFront Distribution {distribution.id} is using a deprecated SSL protocol."

View File

@@ -8,7 +8,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered
from prowler.providers.aws.lib.service.service import AWSService
################## CloudFront
class CloudFront(AWSService):
def __init__(self, provider):
# Call AWSService's __init__
@@ -30,12 +29,55 @@ class CloudFront(AWSService):
):
distribution_id = item["Id"]
distribution_arn = item["ARN"]
origins = item["Origins"]["Items"]
origin_groups = item.get("OriginGroups", {}).get(
"Items", []
)
origin_failover = all(
origin_group.get("Members", {}).get("Quantity", 0) >= 2
for origin_group in origin_groups
)
default_certificate = item["ViewerCertificate"][
"CloudFrontDefaultCertificate"
]
certificate = item["ViewerCertificate"].get(
"Certificate", ""
)
ssl_support_method = SSLSupportMethod(
item["ViewerCertificate"].get(
"SSLSupportMethod", "static-ip"
)
)
origins = []
for origin in item.get("Origins", {}).get("Items", []):
origins.append(
Origin(
id=origin["Id"],
domain_name=origin["DomainName"],
origin_protocol_policy=origin.get(
"CustomOriginConfig", {}
).get("OriginProtocolPolicy", ""),
origin_ssl_protocols=origin.get(
"CustomOriginConfig", {}
)
.get("OriginSslProtocols", {})
.get("Items", []),
origin_access_control=origin.get(
"OriginAccessControlId", ""
),
s3_origin_config=origin.get(
"S3OriginConfig", {}
),
)
)
distribution = Distribution(
arn=distribution_arn,
id=distribution_id,
origins=origins,
region=region,
origin_failover=origin_failover,
ssl_support_method=ssl_support_method,
default_certificate=default_certificate,
certificate=certificate,
)
self.distributions[distribution_id] = distribution
@@ -49,6 +91,7 @@ class CloudFront(AWSService):
try:
for distribution_id in distributions.keys():
distribution_config = client.get_distribution_config(Id=distribution_id)
# Global Config
distributions[distribution_id].logging_enabled = distribution_config[
"DistributionConfig"
@@ -63,6 +106,16 @@ class CloudFront(AWSService):
distributions[distribution_id].web_acl_id = distribution_config[
"DistributionConfig"
]["WebACLId"]
distributions[distribution_id].default_root_object = (
distribution_config["DistributionConfig"].get(
"DefaultRootObject", ""
)
)
distributions[distribution_id].viewer_protocol_policy = (
distribution_config["DistributionConfig"][
"DefaultCacheBehavior"
].get("ViewerProtocolPolicy", "")
)
# Default Cache Config
default_cache_config = DefaultCacheConfigBehaviour(
@@ -124,12 +177,29 @@ class GeoRestrictionType(Enum):
whitelist = "whitelist"
class SSLSupportMethod(Enum):
"""Method types that viewer want to accept HTTPS requests from"""
static_ip = "static-ip"
sni_only = "sni-only"
vip = "vip"
class DefaultCacheConfigBehaviour(BaseModel):
realtime_log_config_arn: Optional[str]
viewer_protocol_policy: ViewerProtocolPolicy
field_level_encryption_id: str
class Origin(BaseModel):
id: str
domain_name: str
origin_protocol_policy: str
origin_ssl_protocols: list[str]
origin_access_control: Optional[str]
s3_origin_config: Optional[dict]
class Distribution(BaseModel):
"""Distribution holds a CloudFront Distribution resource"""
@@ -139,6 +209,12 @@ class Distribution(BaseModel):
logging_enabled: bool = False
default_cache_config: Optional[DefaultCacheConfigBehaviour]
geo_restriction_type: Optional[GeoRestrictionType]
origins: list
origins: list[Origin]
web_acl_id: str = ""
default_certificate: Optional[bool]
default_root_object: Optional[str]
viewer_protocol_policy: Optional[str]
tags: Optional[list] = []
origin_failover: Optional[bool]
ssl_support_method: Optional[SSLSupportMethod]
certificate: Optional[str]

View File

@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsS3Bucket",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure that all your AWS CloudTrail trails are configured to log Data events in order to record S3 object-level API operations, such as GetObject, DeleteObject and PutObject, for individual S3 buckets or for all current and future S3 buckets provisioned in your AWS account.",
"Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "",

View File

@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "low",
"ResourceType": "AwsS3Bucket",
"ResourceType": "AwsCloudTrailTrail",
"Description": "Ensure that all your AWS CloudTrail trails are configured to log Data events in order to record S3 object-level API operations, such as GetObject, DeleteObject and PutObject, for individual S3 buckets or for all current and future S3 buckets provisioned in your AWS account.",
"Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.",
"RelatedUrl": "",

View File

@@ -39,14 +39,30 @@ class cloudtrail_threat_detection_enumeration(Check):
minutes=threat_detection_minutes,
):
event_log = json.loads(event_log["CloudTrailEvent"])
if ".amazonaws.com" not in event_log["sourceIPAddress"]:
if event_log["sourceIPAddress"] not in potential_enumeration:
potential_enumeration[event_log["sourceIPAddress"]] = set()
potential_enumeration[event_log["sourceIPAddress"]].add(
event_name
)
for source_ip, actions in potential_enumeration.items():
ip_threshold = round(len(actions) / len(enumeration_actions), 2)
if (
"arn" in event_log["userIdentity"]
): # Ignore event logs without ARN since they are AWS services
if (
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
) not in potential_enumeration:
potential_enumeration[
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
] = set()
potential_enumeration[
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
].add(event_name)
for aws_identity, actions in potential_enumeration.items():
identity_threshold = round(len(actions) / len(enumeration_actions), 2)
aws_identity_type = aws_identity[1]
aws_identity_arn = aws_identity[0]
if len(actions) / len(enumeration_actions) > threshold:
found_potential_enumeration = True
report = Check_Report_AWS(self.metadata())
@@ -56,7 +72,7 @@ class cloudtrail_threat_detection_enumeration(Check):
cloudtrail_client.region
)
report.status = "FAIL"
report.status_extended = f"Potential enumeration attack detected from source IP {source_ip} with an threshold of {ip_threshold}."
report.status_extended = f"Potential enumeration attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}."
findings.append(report)
if not found_potential_enumeration:
report = Check_Report_AWS(self.metadata())

View File

@@ -40,19 +40,31 @@ class cloudtrail_threat_detection_privilege_escalation(Check):
minutes=threat_detection_minutes,
):
event_log = json.loads(event_log["CloudTrailEvent"])
if ".amazonaws.com" not in event_log["sourceIPAddress"]:
if (
"arn" in event_log["userIdentity"]
): # Ignore event logs without ARN since they are AWS services
if (
event_log["sourceIPAddress"]
not in potential_privilege_escalation
):
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
) not in potential_privilege_escalation:
potential_privilege_escalation[
event_log["sourceIPAddress"]
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
] = set()
potential_privilege_escalation[
event_log["sourceIPAddress"]
(
event_log["userIdentity"]["arn"],
event_log["userIdentity"]["type"],
)
].add(event_name)
for source_ip, actions in potential_privilege_escalation.items():
ip_threshold = round(len(actions) / len(privilege_escalation_actions), 2)
for aws_identity, actions in potential_privilege_escalation.items():
identity_threshold = round(
len(actions) / len(privilege_escalation_actions), 2
)
aws_identity_type = aws_identity[1]
aws_identity_arn = aws_identity[0]
if len(actions) / len(privilege_escalation_actions) > threshold:
found_potential_privilege_escalation = True
report = Check_Report_AWS(self.metadata())
@@ -62,7 +74,7 @@ class cloudtrail_threat_detection_privilege_escalation(Check):
cloudtrail_client.region
)
report.status = "FAIL"
report.status_extended = f"Potential privilege escalation attack detected from source IP {source_ip} with an threshold of {ip_threshold}."
report.status_extended = f"Potential privilege escalation attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}."
findings.append(report)
if not found_potential_privilege_escalation:
report = Check_Report_AWS(self.metadata())

View File

@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudTrailTrail",
"ResourceType": "AwsCloudWatchAlarm",
"Description": "Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL).",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",

View File

@@ -15,24 +15,24 @@ class cloudwatch_changes_to_network_acls_alarm_configured(Check):
def execute(self):
pattern = r"\$\.eventName\s*=\s*.?CreateNetworkAcl.+\$\.eventName\s*=\s*.?CreateNetworkAclEntry.+\$\.eventName\s*=\s*.?DeleteNetworkAcl.+\$\.eventName\s*=\s*.?DeleteNetworkAclEntry.+\$\.eventName\s*=\s*.?ReplaceNetworkAclEntry.+\$\.eventName\s*=\s*.?ReplaceNetworkAclAssociation.?"
findings = []
if (
cloudtrail_client.trails is not None
and logs_client.metric_filters is not None
and cloudwatch_client.metric_alarms is not None
):
report = Check_Report_AWS(self.metadata())
report.status = "FAIL"
report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated."
report.region = logs_client.region
report.resource_id = logs_client.audited_account
report.resource_arn = logs_client.log_group_arn_template
report = check_cloudwatch_log_metric_filter(
pattern,
cloudtrail_client.trails,
logs_client.metric_filters,
cloudwatch_client.metric_alarms,
report,
)
report = check_cloudwatch_log_metric_filter(
pattern,
cloudtrail_client.trails,
logs_client.metric_filters,
cloudwatch_client.metric_alarms,
self.metadata(),
)
if report is not None:
if report == Check_Report_AWS(self.metadata()):
report.status = "FAIL"
report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated."
report.region = logs_client.region
report.resource_id = logs_client.audited_account
report.resource_arn = logs_client.log_group_arn_template
report.resource_tags = []
findings.append(report)
return findings

View File

@@ -9,7 +9,7 @@
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:cloudwatch:region:account-id:certificate/resource-id",
"Severity": "medium",
"ResourceType": "AwsCloudTrailTrail",
"ResourceType": "AwsCloudWatchAlarm",
"Description": "Ensure a log metric filter and alarm exist for changes to network gateways.",
"Risk": "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity.",
"RelatedUrl": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html",

View File

@@ -15,24 +15,24 @@ class cloudwatch_changes_to_network_gateways_alarm_configured(Check):
def execute(self):
pattern = r"\$\.eventName\s*=\s*.?CreateCustomerGateway.+\$\.eventName\s*=\s*.?DeleteCustomerGateway.+\$\.eventName\s*=\s*.?AttachInternetGateway.+\$\.eventName\s*=\s*.?CreateInternetGateway.+\$\.eventName\s*=\s*.?DeleteInternetGateway.+\$\.eventName\s*=\s*.?DetachInternetGateway.?"
findings = []
if (
cloudtrail_client.trails is not None
and logs_client.metric_filters is not None
and cloudwatch_client.metric_alarms is not None
):
report = Check_Report_AWS(self.metadata())
report.status = "FAIL"
report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated."
report.region = logs_client.region
report.resource_id = logs_client.audited_account
report.resource_arn = logs_client.log_group_arn_template
report = check_cloudwatch_log_metric_filter(
pattern,
cloudtrail_client.trails,
logs_client.metric_filters,
cloudwatch_client.metric_alarms,
report,
)
report = check_cloudwatch_log_metric_filter(
pattern,
cloudtrail_client.trails,
logs_client.metric_filters,
cloudwatch_client.metric_alarms,
self.metadata(),
)
if report is not None:
if report == Check_Report_AWS(self.metadata()):
report.status = "FAIL"
report.status_extended = "No CloudWatch log groups found with metric filters or alarms associated."
report.region = logs_client.region
report.resource_id = logs_client.audited_account
report.resource_arn = logs_client.log_group_arn_template
report.resource_tags = []
findings.append(report)
return findings

Some files were not shown because too many files have changed in this diff Show More