Compare commits

...

295 Commits

Author SHA1 Message Date
Nacho Rivera
722554ad3f chore(mitre azure): add mapping to mitre for azure provider (#3857)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-04-30 17:34:10 +02:00
Sergio Garcia
484cf6f49d fix(metadata): remove semicolons from metadata texts (#3830) 2024-04-30 14:02:43 +02:00
tianzedavid
e4154ed4a2 chore: fix some comments (#3900) 2024-04-30 13:43:55 +02:00
Sergio Garcia
86cb9f5838 fix(vpc): solve AWS principal key error (#3903) 2024-04-30 13:29:58 +02:00
Sergio Garcia
1622d0aa35 fix(vpc): solve subnet route key error (#3902) 2024-04-30 13:09:31 +02:00
Sergio Garcia
b54ecb50bf fix(efs): check all public conditions (#3872) 2024-04-30 13:08:05 +02:00
dependabot[bot]
f16857fdf1 chore(deps): bump boto3 from 1.34.84 to 1.34.94 (#3894)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 12:50:07 +02:00
Rubén De la Torre Vico
ab109c935c docs(unit-testing): Add GCP services documentation (#3901) 2024-04-30 12:49:51 +02:00
dependabot[bot]
8e7e456431 chore(deps-dev): bump black from 24.4.0 to 24.4.2 (#3883)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 12:14:58 +02:00
dependabot[bot]
46114cd5f4 chore(deps-dev): bump moto from 5.0.5 to 5.0.6 (#3882)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 11:22:46 +02:00
dependabot[bot]
275e509c8d chore(deps): bump azure-mgmt-compute from 30.6.0 to 31.0.0 (#3880)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 10:37:48 +02:00
dependabot[bot]
12f135669f chore(deps-dev): bump coverage from 7.4.4 to 7.5.0 (#3879)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 10:11:56 +02:00
dependabot[bot]
f004df673d chore(deps-dev): bump pytest from 8.1.1 to 8.2.0 (#3878)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 09:46:18 +02:00
dependabot[bot]
3ed24b5d7a chore(deps-dev): bump pytest-xdist from 3.5.0 to 3.6.1 (#3877)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 09:07:12 +02:00
dependabot[bot]
77eade01a2 chore(deps): bump botocore from 1.34.89 to 1.34.94 (#3876)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 08:19:05 +02:00
dependabot[bot]
a2158983f7 chore(deps): bump trufflesecurity/trufflehog from 3.73.0 to 3.74.0 (#3874)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 07:50:48 +02:00
dependabot[bot]
c0d57c9498 chore(deps-dev): bump freezegun from 1.4.0 to 1.5.0 (#3875)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-30 07:49:41 +02:00
Sergio Garcia
35c8ea5e3f fix(aws): not show findings when AccessDenieds (#3803) 2024-04-29 17:42:44 +02:00
Sergio Garcia
b36152484d chore(docs): update BridgeCrew links in metadata to our local docs link (#3858)
Co-authored-by: puchy22 <rubendltv22@gmail.com>
2024-04-29 17:39:04 +02:00
Rubén De la Torre Vico
768ca3f0ce test(gcp): Add new services tests to GCP (#3796)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2024-04-29 12:24:44 +02:00
Kay Agahd
bedd05c075 fix(aws): Extend opensearch_service_domains_use_cognito_authentication_for_kibana with SAML (#3864) 2024-04-29 12:08:03 +02:00
Sergio Garcia
721f73fdbe chore(gcp): handle list projects API call errors (#3849)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-29 11:32:21 +02:00
Sergio Garcia
34c2128d88 chore(docs): solve some issues (#3868) 2024-04-29 10:19:37 +02:00
Pedro Martín
14de3acdaa docs(audit_info): update docs about audit info and new testing (#3831)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-29 09:40:18 +02:00
Matt Merchant
899b2f8eb6 chore(get_tagged_resources): Add return value type hint (#3860)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2024-04-26 15:23:16 +02:00
Nacho Rivera
27bb05fedc chore(regions_update): Changes in regions for AWS services. (#3862)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-26 11:57:32 +02:00
Pedro Martín
e1909b8ad9 fix(s3-integration): Store compliance outputs in their folder (#3859) 2024-04-26 08:22:36 +02:00
Pedro Martín
0ed7a247b6 fix(KeyError): handle CacheSubnetGroupName keyError (#3856) 2024-04-26 08:17:30 +02:00
Pedro Martín
ee46bf3809 feat(json-ocsf): Add new fields for py-ocsf 0.1.0 (#3853) 2024-04-25 12:47:28 +02:00
Nacho Rivera
469254094b chore(regions_update): Changes in regions for AWS services. (#3855)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-25 12:09:23 +02:00
Pedro Martín
acac3fc693 feat(ec2): Add 2 new checks + fixers related with EC2 service (#3827)
Co-authored-by: Sergio <sergio@prowler.com>
2024-04-24 11:43:19 +02:00
Nacho Rivera
022b7ef756 chore(regions_update): Changes in regions for AWS services. (#3848)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-24 11:29:26 +02:00
dependabot[bot]
69d4f55734 chore(deps): bump google-api-python-client from 2.125.0 to 2.127.0 (#3844)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-24 10:12:49 +02:00
dependabot[bot]
a0bff4b859 chore(deps): bump botocore from 1.34.84 to 1.34.89 (#3836)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-24 09:38:20 +02:00
Nacho Rivera
23df599a03 chore(regions_update): Changes in regions for AWS services. (#3842)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-23 17:48:34 +02:00
dependabot[bot]
c8d74ca350 chore(deps): bump azure-mgmt-containerservice from 29.1.0 to 30.0.0 (#3835)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-23 17:48:15 +02:00
dependabot[bot]
8d6ba43ad0 chore(deps): bump msgraph-sdk from 1.2.0 to 1.3.0 (#3834)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-23 08:29:03 +02:00
Nacho Rivera
44ca2f7a66 chore(regions_update): Changes in regions for AWS services. (#3826)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-22 12:48:42 +02:00
Pepe Fagoaga
ec0be1c7fe chore(check): global_provider is not needed here (#3828) 2024-04-22 12:05:41 +02:00
Pepe Fagoaga
fd732db91b fix(mutelist): Be called whatever the provider (#3811) 2024-04-22 11:16:21 +02:00
Pepe Fagoaga
67f45b7767 chore(release): 4.1.0 (#3817) 2024-04-22 09:40:37 +02:00
Nacho Rivera
396e6a1c36 chore(regions_update): Changes in regions for AWS services. (#3824)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-22 09:39:04 +02:00
Jakob Rieck
326c46defd fix(aws): Corrects privilege escalation vectors (#3823) 2024-04-19 13:42:51 +02:00
Jakob Rieck
7a1762be51 fix(aws): Include record names for dangling IPs (#3821)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-19 12:47:03 +02:00
Nacho Rivera
b466b476a3 chore(regions_update): Changes in regions for AWS services. (#3822)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-19 11:32:22 +02:00
Pepe Fagoaga
e4652d4339 fix(ocsf): Add resource details to data (#3819) 2024-04-19 08:35:26 +02:00
Pepe Fagoaga
f1e4cd3938 docs(ocsf): Add missing fields to the example (#3816) 2024-04-19 08:09:36 +02:00
dependabot[bot]
e192a98079 chore(deps): bump aiohttp from 3.9.3 to 3.9.4 (#3818)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 07:50:48 +02:00
Pedro Martín
833dc83922 fix(dashboard): fix error in windows for csvreader (#3806) 2024-04-18 15:27:20 +02:00
Pedro Martín
ab1751c595 fix(overview-table): change font in overview table (#3815) 2024-04-18 14:53:32 +02:00
Sergio Garcia
fff06f971e chore(vpc): improve public subnet logic (#3814) 2024-04-18 13:58:42 +02:00
Pepe Fagoaga
a138d2964e fix(execute_check): Handle ModuleNotFoundError (#3812) 2024-04-18 12:36:15 +02:00
Pedro Martín
e6d7965453 fix(network_azure): handle capitalized protocols in security group rules (#3808) 2024-04-18 08:11:29 +02:00
Sergio Garcia
ab714f0fc7 chore(fixer): add more fixers (#3772)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-18 08:09:03 +02:00
Sergio Garcia
465b0f6a16 fix(utils): import libraries when needed (#3805) 2024-04-17 16:35:04 +02:00
Pedro Martín
bd87351ea7 chore(aws): Add CloudTrail Threat Detection tests (#3804)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-17 14:01:39 +02:00
Sergio Garcia
d79ec44e4c chore(ec2): improve handling of ENIs (#3798) 2024-04-17 13:12:31 +02:00
Matt Merchant
a2f84a12ea docs(developer guide): fix broken link (#3799) 2024-04-17 10:56:35 +02:00
Sergio Garcia
6fd71356ee chore(rds): improve rds public instance check (#3797) 2024-04-16 15:01:47 +02:00
dependabot[bot]
a0a305d9b1 chore(deps): bump pandas from 2.2.1 to 2.2.2 (#3791)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 14:18:18 +02:00
dependabot[bot]
6396d90fa6 chore(deps): bump azure-identity from 1.15.0 to 1.16.0 (#3795)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 12:13:51 +02:00
dependabot[bot]
e324750ec2 chore(deps-dev): bump mkdocs-material from 9.5.17 to 9.5.18 (#3794)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 11:46:21 +02:00
dependabot[bot]
5d99f020fa chore(deps): bump boto3 from 1.34.80 to 1.34.84 (#3793)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 11:17:55 +02:00
Sergio Garcia
b82e928f58 chore(dependabot): increase PRs limit (#3789) 2024-04-16 10:43:53 +02:00
dependabot[bot]
da871897e6 chore(deps): bump dash-bootstrap-components from 1.5.0 to 1.6.0 (#3778)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 10:20:08 +02:00
Pedro Martín
81778f73e4 fix(table-overview): Multiple changes on dashboard table from overview (#3773) 2024-04-16 10:15:16 +02:00
dependabot[bot]
2623728518 chore(deps): bump botocore from 1.34.80 to 1.34.84 (#3779)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 09:36:10 +02:00
dependabot[bot]
97f1d1b476 chore(deps): bump boto3 from 1.34.77 to 1.34.80 (#3780)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 08:56:14 +02:00
dependabot[bot]
2f6a837bc0 chore(deps): bump trufflesecurity/trufflehog from 3.72.0 to 3.73.0 (#3786)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 07:23:02 +02:00
dependabot[bot]
5e22c2d9a5 chore(deps-dev): bump black from 24.3.0 to 24.4.0 (#3777)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 07:22:28 +02:00
Sergio Garcia
99bd637de4 chore(fixer): improve fixer logic and include more (#3750) 2024-04-15 17:45:40 +02:00
Sergio Garcia
b9177e5580 fix(trufflehog): fix GitHub action of TruffleHog (#3775) 2024-04-15 17:37:07 +02:00
Pepe Fagoaga
fc7ec184d9 fix(slack): Use global provider object (#3770) 2024-04-15 14:47:38 +02:00
Rubén De la Torre Vico
7a6ca342af docs(unit-testing): Update the unit testing section (#3764)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2024-04-15 13:20:31 +02:00
Rubén De la Torre Vico
30b6e5e5c6 docs(devel-guide): Add provider section and remove audit_info section (#3756)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2024-04-15 13:12:28 +02:00
Pepe Fagoaga
f8476decf7 fix(security-hub): MUTED -> WARNING (#3768) 2024-04-15 09:58:18 +02:00
Nacho Rivera
49e238577c chore(regions_update): Changes in regions for AWS services. (#3765)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-15 08:50:07 +02:00
Rubén De la Torre Vico
026fff79c6 docs(devel-guide): Adding some improves and clarifications to developer guide (#3749)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2024-04-12 12:55:46 +02:00
Pedro Martín
36c3870c2f docs(compliance): Change images for compliance (#3760) 2024-04-12 12:30:33 +02:00
Pepe Fagoaga
54c309dbda fix(ocsf): Add compliance (#3753)
Co-authored-by: pedrooot <pedromarting3@gmail.com>
2024-04-12 12:28:34 +02:00
Pepe Fagoaga
f00dd35f93 chore(codeowners): Add prowler-dev team (#3763) 2024-04-12 12:27:28 +02:00
Pepe Fagoaga
e040efb3c8 fix(mutelist): if all fails are muted do exit 0 (#3754) 2024-04-12 12:26:58 +02:00
Pedro Martín
805d50586b fix(compliance): Add muted info to compliance outputs (#3751) 2024-04-12 12:19:20 +02:00
Pedro Martín
a289a807c5 fix(wafv2): Handle WAFNonexistentItemException (#3761) 2024-04-12 12:05:50 +02:00
Pedro Martín
e9117f95ee fix(json-ocsf): Remove risk field from unmapped (#3759)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-12 10:55:52 +02:00
Pedro Martín
82bd4e940f docs(threat-detection): Add threat-detection docs (#3757) 2024-04-12 10:36:55 +02:00
dependabot[bot]
ad3b0b33f2 chore(deps): bump idna from 3.6 to 3.7 (#3758)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-12 08:12:30 +02:00
Nacho Rivera
b2b664a5b0 chore(regions_update): Changes in regions for AWS services. (#3755)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-11 14:43:26 +02:00
Pepe Fagoaga
571f3ebe1d fix(ocsf): Include check_id as metadata.event_code (#3748) 2024-04-10 15:51:48 +02:00
Pepe Fagoaga
c7f09df4e7 chore(dashboard): Use Prowler CLI parser (#3722) 2024-04-10 15:49:21 +02:00
Sergio Garcia
8758ecae97 feat(gcp): improve Google Projects scan customization (#3741) 2024-04-10 13:16:47 +02:00
Pedro Martín
f13c843ba6 fix(json-ocsf): Add missing fields for JSON-OCSF (#3745) 2024-04-10 11:55:48 +02:00
Pedro Martín
e95f7dd540 docs(outputs): update docs for v4 outputs (#3734)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-10 11:54:41 +02:00
Nacho Rivera
693329b87e chore(regions_update): Changes in regions for AWS services. (#3746)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-10 11:53:27 +02:00
Rubén De la Torre Vico
f1ad521f64 feat(docs): Support toggle light/dark mode (#3744) 2024-04-10 10:37:44 +02:00
Pedro Martín
82fbba6513 fix(json-ocsf): add check_id field in json-ocsf output (#3740) 2024-04-10 09:58:33 +02:00
Pedro Martín
66fba8e4cd fix(download): remove dataframe index from download in dashboard (#3739) 2024-04-10 08:41:50 +02:00
Pepe Fagoaga
417131fa36 docs: readme points to docs.prowler.com to learn everything (#3707)
Co-authored-by: Sergio <sergio@prowler.com>
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-04-09 16:28:01 +02:00
Sergio Garcia
9c9d270053 fix(ulimit): import library only in windows (#3738) 2024-04-09 15:36:05 +02:00
Pedro Martín
f7fab165ba fix(aws_lambda): Update obsolete lambda runtimes (#3735) 2024-04-09 15:08:19 +02:00
Pepe Fagoaga
93bdf43c95 fix(actions): Don't need expressions within if (#3733) 2024-04-09 13:33:53 +02:00
Pepe Fagoaga
b3866b5b71 docs(dashboard): format list (#3732) 2024-04-09 13:18:52 +02:00
Sergio Garcia
2308084dee chore(version): update Prowler version (#3730) 2024-04-09 13:18:00 +02:00
Pepe Fagoaga
6eb5496c27 docs(dashboard): Indicate how to change port (#3729) 2024-04-09 12:28:54 +02:00
Nacho Rivera
c5514fdb63 chore(regions_update): Changes in regions for AWS services. (#3727)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-09 12:03:06 +02:00
Pedro Martín
c78c3058fd fix(service_name): fix typo in ServiceName field (#3723) 2024-04-09 11:39:02 +02:00
Pepe Fagoaga
10d9ef9906 chore(dispatch): just for v3 (#3712) 2024-04-09 11:33:00 +02:00
Pepe Fagoaga
43426041ef docs(mutelist): remove MUTED and explain new fields (#3726) 2024-04-09 11:18:07 +02:00
Sergio Garcia
125eb9ac53 fix(k8s): improve kubernetes deployment (#3713) 2024-04-09 10:45:58 +02:00
Pedro Martín
681407e0a2 fix(compliance): add field ModoEjecucion in csv output for ENS (#3719) 2024-04-09 10:26:06 +02:00
Pedro Martín
082f3a8fe8 fix(dashboard): Add multiple dashboard fixes (#3714) 2024-04-09 10:22:03 +02:00
Sergio Garcia
397cc26b2a fix(gcp): add project id to outputs (#3711) 2024-04-09 10:17:32 +02:00
Rubén De la Torre Vico
331ae92843 chore(Azure): Optimize Entra service to use async funcs (#3706) 2024-04-09 09:20:06 +02:00
dependabot[bot]
06843cd41a chore(deps): bump botocore from 1.34.77 to 1.34.80 (#3715)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 08:39:56 +02:00
Pedro Martín
28b5ef9ee9 fix(ens): add dependencias field ENS rd2022 compliance (#3701) 2024-04-09 08:29:41 +02:00
Pedro Martín
63dcc057d3 feat(dashboard): add correct label for each dropdown (#3700) 2024-04-08 17:50:48 +02:00
Sergio Garcia
0bc16ee5ff chore(Dockerfile): remove deprecated dash dependencies (#3708) 2024-04-08 14:58:19 +02:00
Sergio Garcia
abcc9c2c80 docs(images): fix images link in documentation (#3709) 2024-04-08 14:49:06 +02:00
Sergio Garcia
daf2ad38bd chore(docs): update CloudShell scripts (#3687) 2024-04-08 14:39:29 +02:00
Sergio Garcia
3dc418df39 chore(action): update python version to 3.12 in GH action (#3705) 2024-04-08 12:48:54 +02:00
dependabot[bot]
00aaafbc12 chore(deps-dev): bump moto from 5.0.4 to 5.0.5 (#3681)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 12:47:22 +02:00
Sergio Garcia
bd49a55f3d chore(Dockerfile): update Python version to 3.12 (#3699) 2024-04-08 12:22:49 +02:00
dependabot[bot]
013975b7a6 chore(deps): bump kubernetes from 28.1.0 to 29.0.0 (#3679)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 12:21:23 +02:00
Pepe Fagoaga
392026286a fix(actions): use LATEST_TAG for v4 (#3703) 2024-04-08 12:10:02 +02:00
Nacho Rivera
29ef974565 chore(regions_update): Changes in regions for AWS services. (#3693)
Co-authored-by: sergargar <38561120+sergargar@users.noreply.github.com>
2024-04-08 11:52:51 +02:00
Sergio Garcia
06c8216092 build(deps): Update boto3 to version 1.34.77 (#3669)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-08 11:51:22 +02:00
Pepe Fagoaga
03f04d24a5 chore(dependabot): Add v3 label (#3698) 2024-04-08 11:19:35 +02:00
Pedro Martín
7b45ed63cc docs(dashboard): improve dashboard documentation (#3688) 2024-04-08 11:10:30 +02:00
Sergio Garcia
6e4dd1d69c fix(k8s): sanitize context syntax only for output file names (#3689) 2024-04-08 11:08:35 +02:00
Sergio Garcia
185b4cba0c chore(mutelist): remove space within mutelist name (#3690) 2024-04-08 11:07:29 +02:00
Pepe Fagoaga
8198ea4a2c chore(dependabot): Run also for v3 branch (#3683) 2024-04-08 11:05:09 +02:00
dependabot[bot]
aaf3e8a5cf chore(deps): bump google-api-python-client from 2.124.0 to 2.125.0 (#3678)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 10:55:00 +02:00
dependabot[bot]
ecef56fa8f chore(deps): bump trufflesecurity/trufflehog from 3.71.2 to 3.72.0 (#3677)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 10:39:53 +02:00
Pepe Fagoaga
349ce3f2d0 chore(regions): Add backport-v3 label (#3684) 2024-04-08 10:31:28 +02:00
Sergio Garcia
e3d4741213 chore(merge): include latest changes of v3 (#3686)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
Co-authored-by: Nacho Rivera <nachor1992@gmail.com>
2024-04-08 10:30:39 +02:00
Pepe Fagoaga
9d6d5f1d76 fix(args): Handle default argument (#3674) 2024-04-08 10:01:35 +02:00
Pepe Fagoaga
3152d67f58 chore(actions): Run for master and v3 (#3685) 2024-04-08 09:35:23 +02:00
Pepe Fagoaga
cb41c8d15b fix(dockerfile): add missing path to build (#3680) 2024-04-08 09:24:05 +02:00
Pepe Fagoaga
06590842d6 chore(action): Run for v4 branch (#3666) 2024-04-04 15:53:45 +02:00
Pedro Martín
d4c22a0ca5 fix(dashboard): handle Kubernetes CIS in EKS context (#3671) 2024-04-04 15:50:38 +02:00
Sergio Garcia
c6f9936292 fix(merge): update v4 with latest changes (#3670) 2024-04-04 15:37:41 +02:00
Sergio Garcia
eaa8900758 fix(threat detection): rename to threshold (#3665) 2024-04-04 13:29:35 +02:00
Pedro Martín
e1e95d8879 docs(Dashboard): Add docs for dashboards (#3655) 2024-04-04 13:26:42 +02:00
Pedro Martín
ef3a0f4878 fix(Dashboard): Multiple dashboard fixes (#3654)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-04-04 13:14:27 +02:00
Sergio Garcia
64cc36e7e2 fix(fixer): list fixers without sufix (#3660)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-04 13:07:54 +02:00
Sergio Garcia
1e001bb0fd fix(deps): solve dependencies (#3662) 2024-04-04 12:48:59 +02:00
Sergio Garcia
6ba123a003 fix(box): remove lines inside box (#3657) 2024-04-04 12:24:14 +02:00
Pepe Fagoaga
36d0f2c23f fix: typo in action (#3659) 2024-04-04 12:04:45 +02:00
Sergio Garcia
63412e3645 chore(merge): update v4 with latest changes of v3 (#3653) 2024-04-03 18:31:36 +02:00
Pedro Martín
191cf276c3 feat(dashboards): add new Prowler dashboards (#3575)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-04-02 18:12:16 +02:00
Sergio Garcia
45978bd0bb feat(fixer): add Prowler Fixer feature! (#3634) 2024-04-02 17:13:26 +02:00
Sergio Garcia
9666652d18 chore(readme): update k8s cis (#3640) 2024-04-02 14:37:20 +02:00
Sergio Garcia
ad2716d7c9 chore(compliance): only execute all compliances in normal execution (#3635) 2024-04-02 10:55:38 +02:00
Sergio Garcia
0a7939bea3 chore(args): add plural severity argument (#3636) 2024-04-02 10:21:42 +02:00
Sergio Garcia
b8c50a7b45 chore(backport): merge changes from v3 to v4 (#3625)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
Co-authored-by: Nacho Rivera <nachor1992@gmail.com>
Co-authored-by: Gabriel Soltz <8935378+gabrielsoltz@users.noreply.github.com>
Co-authored-by: Hugo966 <148140670+Hugo966@users.noreply.github.com>
Co-authored-by: Kay Agahd <kagahd@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-04-02 08:52:21 +02:00
Sergio Garcia
175e8d2b05 chore(slogan): update Prowler slogan (#3619) 2024-04-01 12:19:14 +02:00
Pepe Fagoaga
046069a656 chore(categories): Add threat detection checks in the loader (#3622) 2024-04-01 11:48:46 +02:00
Sergio Garcia
f9522da48f feat(cloudtrail): add threat detection checks for AWS (enum and priv escalation) (#3602) 2024-03-27 16:23:00 +01:00
Sergio Garcia
c03f959005 chore(ulimit): handle low ulimit value on shell session for POSIX if max open files is below 4096 (#3601) 2024-03-27 14:52:14 +01:00
Sergio Garcia
522aeebe5e chore(args): sanitize arguments (#3611) 2024-03-27 14:14:21 +01:00
Sergio Garcia
5312f487f9 chore(report): improve shown report in UI (#3587) 2024-03-27 12:57:20 +01:00
Pedro Martín
d9b6624d65 feat(compliance): Add CIS 1.8 framework for Kubernetes (#3600)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-03-26 14:03:39 +01:00
Hugo966
1506da54fc feat(azure): locations added to Azure findings (#3596) 2024-03-22 15:41:58 +01:00
Sergio Garcia
245512d320 fix(providers): import modules also from outside of directory (#3595) 2024-03-22 13:36:21 +01:00
Pepe Fagoaga
487190b379 fix(securityhub): Add validation and handle errors (#3590)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-22 10:27:48 +01:00
Sergio Garcia
74aaeaa95c fix(mapping): handle None attributes in data (#3588)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-03-21 17:56:21 +01:00
Sergio Garcia
28e8f0de2b chore(merge): get latest changes from v3 to v4 (#3582)
Co-authored-by: Hugo966 <148140670+Hugo966@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
Co-authored-by: Nacho Rivera <nachor1992@gmail.com>
2024-03-21 17:08:19 +01:00
Pedro Martín
f60b5017e2 fix(compliance): fix csv output for framework Mitre Attack (#3574) 2024-03-21 13:18:03 +01:00
Sergio Garcia
fe80821596 chore(muted): handle new Muted status (#3570) 2024-03-19 18:37:49 +01:00
Pepe Fagoaga
628a3c4e7b fix(quickinventory): Adapt for the new AWS provider class (#3569)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-19 16:14:01 +01:00
Sergio Garcia
3d59c34ec9 chore(merge): add new changes from v3 (#3549)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
Co-authored-by: Nacho Rivera <nachor1992@gmail.com>
Co-authored-by: Rubén De la Torre Vico <rubendltv22@gmail.com>
Co-authored-by: Pedro Martín <pedromarting3@gmail.com>
Co-authored-by: Hugo966 <148140670+Hugo966@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Hugo Gálvez Ureña <hugogalvezu96@gmail.com>
Co-authored-by: github-actions <noreply@github.com>
2024-03-19 15:54:41 +01:00
Sergio Garcia
35043c2dd6 chore(unused services): scan unused services by default and add flag (#3556)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-03-19 15:15:19 +01:00
Pepe Fagoaga
ab815123c9 chore(slack): fix integration with provider (#3565)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-19 11:24:11 +01:00
Pepe Fagoaga
69ab84efe1 chore(main): remove getattr for mutelist (#3564) 2024-03-19 10:58:02 +01:00
Pepe Fagoaga
77823afa54 chore(audit_info): Replace for provider and add tests (#3542)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-03-19 09:53:05 +01:00
Pepe Fagoaga
63cd6c1290 chore(mutelist): enforce for all providers (#3554) 2024-03-18 10:12:15 +01:00
Sergio Garcia
cab32d2f94 feat(mutelist): add Mute List for all providers (#3548) 2024-03-15 12:22:10 +01:00
Pepe Fagoaga
1f4316e9dd chore(ocsf): add OCSF 1.1 and organize code (#3517)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-03-14 15:04:47 +01:00
Pepe Fagoaga
ade762a85e fix(azure): use subscriptions in get_locations (#3541) 2024-03-14 14:57:20 +01:00
Pepe Fagoaga
bda5d62c72 chore(aws): Replace audit_info for provider (#3521)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-13 18:15:24 +01:00
Pepe Fagoaga
2176fff8c3 chore(json): deprecate native json (#3514) 2024-03-13 18:11:33 +01:00
Pepe Fagoaga
87893bd54b chore(csv): Common output for all the providers (#3513)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-03-13 17:31:35 +01:00
Sergio Garcia
b539a888b1 chore(compliance): solve compliance issues (#3507)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-03-13 15:43:33 +01:00
Sergio Garcia
d6b2b0ca13 docs(kubernetes): add Kubernetes documentation (#3482) 2024-03-13 15:37:49 +01:00
Pepe Fagoaga
58ee45b702 chore(merge): 2024-03-06 11:03:00 UTC (#3506) 2024-03-06 13:05:31 +01:00
Pepe Fagoaga
c62d97f23a chore(html): deprecate output (#3501)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-05 14:16:23 +01:00
Pepe Fagoaga
d618c5ea12 fix(shodan): Make it available for all the providers (#3500) 2024-03-05 13:55:43 +01:00
Pepe Fagoaga
d8e27f0d33 chore(config): Store in provider (#3498)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-05 10:21:08 +01:00
Sergio Garcia
38496ff646 chore(kubernetes): add outputs fields (#3499) 2024-03-05 10:01:51 +01:00
Pepe Fagoaga
da1084907e chore(providers): Store output options and mutelist (#3497) 2024-03-05 09:56:30 +01:00
Pepe Fagoaga
3385b630e7 chore(azure): working outputs (#3491)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-04 17:59:48 +01:00
Pepe Fagoaga
fc59183045 chore(gcp): working outputs (#3490)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-03-04 17:54:41 +01:00
Pepe Fagoaga
33242079f7 chore(k8s): Working outputs (#3489) 2024-03-04 17:25:14 +01:00
Pepe Fagoaga
086148819c chore(aws): Working outputs (#3488) 2024-03-04 17:17:20 +01:00
Pepe Fagoaga
5df9fd881c chore(aws): Simplify provider (#3481)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-03-04 13:50:54 +01:00
Pepe Fagoaga
bd17d36e7f chore(kubernetes): Working provider (#3475)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-01 14:10:10 +01:00
Pepe Fagoaga
be55fa22fd chore(azure): working version executing checks (#3474) 2024-03-01 13:30:09 +01:00
Pepe Fagoaga
b48b3a5e2e chore(azure): working version executing checks (#3472)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-03-01 11:33:01 +01:00
Sergio Garcia
fc03dd37f1 chore(kubernetes): enhance checks metadata (#3469) 2024-02-29 17:16:28 +01:00
Sergio Garcia
d8bb384689 chore(kubernetes): add strong ciphers config vars (#3470) 2024-02-29 14:48:21 +01:00
Pepe Fagoaga
0b32a10bb8 chore(aws): Remove old provider (#3468) 2024-02-29 13:45:43 +01:00
Pepe Fagoaga
f0c027f54e chore(merge): Merge master with Prowler 4.0 (#3467)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
2024-02-29 11:19:17 +01:00
Sergio Garcia
b0f2f34d3b feat(namespace): add --namespaces argument and solve bugs (#3431) 2024-02-28 19:33:29 +01:00
Sergio Garcia
3e6b76df76 fix(kubernetes): improve in-cluster execution (#3397) 2024-02-28 19:00:33 +01:00
Sergio Garcia
6197cf792d feat(kubelet): add 6 checks of Kubelet configuration files on the worker nodes (#3335)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-02-28 18:32:45 +01:00
Sergio Garcia
3c4e5a14f7 feat(core): add 13 checks of Kubernetes Core service (#3315)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-02-28 13:21:53 +01:00
Sergio Garcia
effc743b6e feat(rbac): add 9 checks of Kubernetes RBAC service (#3314) 2024-02-27 13:54:46 +01:00
Sergio Garcia
364a945d28 feat(kubelet): add 10 checks of Kubernetes Kubelet service (#3302)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-02-26 14:15:35 +01:00
Sergio Garcia
07b9354d18 feat(etcd): add checks for Kubernetes etcd (#3294)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-02-22 16:45:06 +00:00
Sergio Garcia
8b1e537ca5 feat(controllermanager): add checks for Kubernetes Controller Manager (#3291) 2024-02-22 16:55:23 +01:00
Sergio Garcia
6a20e850bc feat(apiserver): new 10 Kubernetes ApiServer checks (#3290) 2024-02-22 10:50:12 +01:00
Sergio Garcia
636892bc9a feat(apiserver): new 10 Kubernetes ApiServer checks (#3289) 2024-02-21 13:29:28 +01:00
Sergio Garcia
b40f32ab57 feat(apiserver): new 9 Kubernetes ApiServer checks (#3288)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-02-21 09:19:50 +01:00
Sergio Garcia
14bab496b5 chore(tests): add kubernetes provider tests (#3265) 2024-02-19 12:50:42 +00:00
Sergio Garcia
3cc367e0a3 feat(kubernetes): add etcd, controllermanager and rbac services (#3261)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2024-02-19 13:19:07 +01:00
Nacho Rivera
36fc575e40 feat(AwsProvider): include new structure for AWS provider (#3252)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2024-01-15 16:55:53 +01:00
Sergio Garcia
24efb34d91 chore(manual status): change INFO to MANUAL status (#3254) 2024-01-09 18:08:00 +01:00
Sergio Garcia
c08e244c95 feat(status): add --status flag (#3238) 2024-01-09 11:35:44 +01:00
Sergio Garcia
c2f8980f1f feat(kubernetes): add Kubernetes provider (#3226)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2024-01-09 10:31:51 +01:00
Sergio Garcia
0ef85b3dee fix(gcp): fix error in generating compliance (#3201) 2023-12-18 12:10:58 +01:00
Sergio Garcia
93a2431211 feat(compliance): execute all compliance by default (#3003)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2023-12-13 17:31:39 +01:00
Nacho Rivera
1fe74937c1 feat(CloudProvider): introduce global provider Azure&GCP (#3069) 2023-12-12 18:05:17 +01:00
Sergio Garcia
6ee016e577 chore(sts-endpoint): deprecate --sts-endpoint-region (#3046)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2023-12-12 17:13:50 +01:00
Sergio Garcia
f7248dfb1c feat(mute list): change allowlist to mute list (#3039)
Co-authored-by: Nacho Rivera <nachor1992@gmail.com>
2023-12-12 16:57:52 +01:00
Nacho Rivera
856afb3966 chore(update): rebase from master (#3067)
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: r3drun3 <simone.ragonesi@sighup.io>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Mastron <14130495+mtronrd@users.noreply.github.com>
Co-authored-by: John Mastron <jmastron@jpl.nasa.gov>
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
Co-authored-by: github-actions <noreply@github.com>
Co-authored-by: simone ragonesi <102741679+R3DRUN3@users.noreply.github.com>
Co-authored-by: Johnny Lu <johnny2lu@gmail.com>
Co-authored-by: Vajrala Venkateswarlu <59252985+venkyvajrala@users.noreply.github.com>
Co-authored-by: Ignacio Dominguez <ignacio.dominguez@zego.com>
2023-11-27 13:58:45 +01:00
Sergio Garcia
bf315261af chore(regions_update): Changes in regions for AWS services. (#2998)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-11-06 10:14:50 +01:00
Kay Agahd
6e83afb580 fix(s3 race condition): catch error if a bucket does not exist any longer (#3000) 2023-11-06 09:24:51 +01:00
Sergio Garcia
1a5742d4f5 fix(cloudtrail): handle HasInsightSelectors key (#2996) 2023-11-02 14:09:27 +01:00
Sergio Garcia
0e22458e86 fix(docs): solve allowlist syntax (#2995) 2023-11-02 12:43:59 +01:00
Sergio Garcia
cd8d1b8a8f chore(regions_update): Changes in regions for AWS services. (#2993)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-11-02 12:27:21 +01:00
Sergio Garcia
141a142742 chore(brew): remove brew action (#2994) 2023-11-02 10:28:32 +01:00
Sergio Garcia
a59b344d20 chore(release): update Prowler Version to 3.11.0 (#2992)
Co-authored-by: github-actions <noreply@github.com>
2023-10-31 15:48:33 +01:00
Pepe Fagoaga
f666711a2a fix(vpc_endpoint_services_allowed_principals_trust_boundaries): Principal (#2991) 2023-10-31 14:19:20 +01:00
Sergio Garcia
1014d64828 fix(outputs): remove empty outputs (#2990) 2023-10-31 14:09:02 +01:00
Sergio Garcia
a126a99853 fix(cis): remove new lines in CIS csv (#2989) 2023-10-31 13:56:33 +01:00
Sergio Garcia
082390a7f0 chore(gcp): print inactive GCP APIs (#2987) 2023-10-31 12:53:53 +01:00
Sergio Garcia
a994553c16 fix(allowlist): verify if allowlist file exists (#2988) 2023-10-31 12:53:45 +01:00
Sergio Garcia
3fd2ae954d fix(elbv2_desync_mitigation_mode): improve logic (#2986) 2023-10-31 12:42:24 +01:00
dependabot[bot]
e17c5642ca build(deps): bump google-api-python-client from 2.104.0 to 2.105.0 (#2985)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 11:36:12 +01:00
Sergio Garcia
fa7968cb1b feat(alias): add check alias functionality (#2971) 2023-10-31 11:25:54 +01:00
dependabot[bot]
57c3183b15 build(deps): bump mkdocs-material from 9.4.6 to 9.4.7 (#2983)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 11:07:29 +01:00
dependabot[bot]
1fd6471cb1 build(deps-dev): bump moto from 4.2.6 to 4.2.7 (#2984)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 10:41:22 +01:00
dependabot[bot]
1827230514 build(deps): bump azure-identity from 1.14.1 to 1.15.0 (#2982)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-31 10:13:32 +01:00
dependabot[bot]
06dc3d3361 build(deps-dev): bump pytest from 7.4.2 to 7.4.3 (#2981)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
2023-10-31 09:28:50 +01:00
Sergio Garcia
a7a2e24d42 chore(docs): allowlist non-default regions (#2980) 2023-10-30 21:52:25 +01:00
ToastyCat
bb543cb5db fix(ec2_instance_imdsv2_enabled ): verify if metadata service is disabled (#2978)
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2023-10-30 21:16:25 +01:00
Pepe Fagoaga
373ce0ad04 fix(GuardDuty): Add enabled_in_account parameter (#2979) 2023-10-30 19:39:22 +01:00
Sergio Garcia
fcb979aae1 feat(allowlist): allowlist non-default regions configuration (#2974) 2023-10-30 17:51:49 +01:00
Pepe Fagoaga
fcc56ad6f7 chore(allowlist): Extract allowlist from report (#2975) 2023-10-30 09:52:59 +01:00
Nacho Rivera
5be8570c8c fix(cloudtrail service): typo in logging info (#2976) 2023-10-30 09:49:20 +01:00
Sergio Garcia
d471442422 chore(regions_update): Changes in regions for AWS services. (#2973)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-27 11:41:08 +02:00
Sergio Garcia
4070c923fc chore(regions_update): Changes in regions for AWS services. (#2969)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-26 15:46:45 +02:00
Sergio Garcia
3ca38fe92d fix(gcp): set always location to lowercase (#2970) 2023-10-26 13:21:09 +02:00
dependabot[bot]
55ebadfe28 build(deps-dev): bump werkzeug from 2.3.4 to 3.0.1 (#2968)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-26 07:58:28 +02:00
Sergio Garcia
9bd2519c83 chore(APIGatewayV2): improve check naming (#2966) 2023-10-25 16:59:06 +02:00
Sergio Garcia
4bfe145be3 chore(regions_update): Changes in regions for AWS services. (#2965)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-25 13:59:19 +02:00
Sergio Garcia
41085049e2 chore(docs): add STS Endpoint and Allowlist updates (#2964) 2023-10-25 13:58:59 +02:00
Sergio Garcia
f7312db0c7 chore(allowlist): prettify allowlist names (#2963) 2023-10-24 18:48:34 +02:00
Sergio Garcia
008534d839 feat(controltower): add AWS Control Tower resources to default Allowlist configuration file (#2953)
Co-authored-by: Toni de la Fuente <toni@blyx.com>
2023-10-24 16:45:21 +02:00
Pepe Fagoaga
8533714cb2 tests: remove tests folder after execution (#2962) 2023-10-24 16:29:18 +02:00
Sergio Garcia
b822c19d2c feat(ignore unused services): add --ignore-unused-services argument to ignore findings from services not in actual use (#2936) 2023-10-24 14:09:27 +02:00
Sergio Garcia
2aa3126eb0 chore(regions_update): Changes in regions for AWS services. (#2961)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-24 11:37:17 +02:00
Sergio Garcia
4c5e85f7ba fix(sts): force v2 STS tokens (#2956) 2023-10-24 10:15:41 +02:00
dependabot[bot]
2b41da4543 build(deps-dev): bump vulture from 2.9.1 to 2.10 (#2960)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 09:46:34 +02:00
dependabot[bot]
f8dc88df6e build(deps): bump google-api-python-client from 2.102.0 to 2.104.0 (#2959)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 09:15:15 +02:00
dependabot[bot]
534033874e build(deps-dev): bump openapi-spec-validator from 0.6.0 to 0.7.1 (#2958)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 08:52:06 +02:00
dependabot[bot]
0851b923fd build(deps-dev): bump pylint from 3.0.1 to 3.0.2 (#2957)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 08:05:18 +02:00
Kay Agahd
fd4bed65a0 docs(v2_v3_mapping): document prowler v3.10.0 changes (#2955) 2023-10-23 15:23:17 +02:00
Nacho Rivera
4746b8b835 feat(report interface): add reporting interface call after report (#2948) 2023-10-23 09:06:51 +02:00
Sergio Garcia
d24eafe6a6 chore(regions_update): Changes in regions for AWS services. (#2954)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-23 07:37:54 +02:00
Sergio Garcia
f3b81edf67 fix(APIGateway): Improve check naming (#2952) 2023-10-20 08:07:08 +02:00
Sergio Garcia
976d0da26e fix(resource filters): add missing resource filters (#2951) 2023-10-19 18:18:58 +02:00
Sergio Garcia
5113b83bc4 chore(create_role_to_assume_cfn.yaml): Add DLM permissions (#2949) 2023-10-19 17:40:07 +02:00
Sergio Garcia
a88877bf7c chore(github): ignore permissions path in GitHub actions (#2950) 2023-10-19 17:37:35 +02:00
Jit
a46d7b2ed9 feat(aws): New Neptune, ElastiCache, APIGW and IAM checks (#2862) 2023-10-19 17:31:51 +02:00
Pepe Fagoaga
170241649d fix(ec2_securitygroup_not_used): Mock Lambda service (#2947) 2023-10-19 17:05:04 +02:00
Sergio Garcia
1ac22bddd6 fix(security group): check if security groups are used by Lambda (#2944) 2023-10-19 12:13:24 +02:00
Sergio Garcia
54fe10ae86 chore(permissions): add DLM permissions (#2946) 2023-10-19 11:45:41 +02:00
Sergio Garcia
33647786e6 chore(regions_update): Changes in regions for AWS services. (#2945)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-19 11:35:19 +02:00
Sergio Garcia
eb3cb97115 feat(vpc): add vpc, nacl or subnet names in findings (#2928) 2023-10-18 16:07:53 +02:00
Sergio Garcia
236f57ab0e chore(regions_update): Changes in regions for AWS services. (#2942)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-18 11:39:45 +02:00
Kay Agahd
c88054107e docs(config): add missing configurable variables (#2941) 2023-10-18 09:10:46 +02:00
dependabot[bot]
c03c7c35d8 build(deps): bump urllib3 from 1.26.17 to 1.26.18 (#2940) 2023-10-18 08:08:11 +02:00
Pepe Fagoaga
b5455215a5 fix(sqs): Handle AWS.SimpleQueueService.NonExistentQueue in list_queue_tags (#2939) 2023-10-17 20:45:22 +02:00
Jit
85e12e9479 feat(aws): New CloudTrail, DLM, DocumentDB, EC2, Account and Support checks (#2675)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2023-10-17 19:00:37 +02:00
Sergio Garcia
f3b7f841fb chore(regions_update): Changes in regions for AWS services. (#2937)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-17 15:55:38 +02:00
Sergio Garcia
92547bfdb6 fix(vpc): ignore com.amazonaws.vpce endpoints (#2929) 2023-10-17 11:14:12 +02:00
dependabot[bot]
3739801ed4 build(deps): bump shodan from 1.30.0 to 1.30.1 (#2935)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 11:03:52 +02:00
dependabot[bot]
a6778a6e27 build(deps-dev): bump moto from 4.2.5 to 4.2.6 (#2934)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 10:40:37 +02:00
dependabot[bot]
f1fc3c63ea build(deps): bump azure-identity from 1.14.0 to 1.14.1 (#2933)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 09:58:42 +02:00
dependabot[bot]
b2a80775a8 build(deps): bump mkdocs-material from 9.4.4 to 9.4.6 (#2932)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 08:48:21 +02:00
dependabot[bot]
1f7f68f6af build(deps): bump azure-storage-blob from 12.18.2 to 12.18.3 (#2931)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 08:25:28 +02:00
Pepe Fagoaga
388678f822 chore(docs): Add report.region criteria (#2930) 2023-10-16 14:50:11 +02:00
Sergio Garcia
1230a3323d chore(regions_update): Changes in regions for AWS services. (#2927)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-10-16 09:42:30 +02:00
Sergio Garcia
02a3c750f8 chore(release): update Prowler Version to 3.10.0 (#2926)
Co-authored-by: github-actions <noreply@github.com>
2023-10-11 17:56:14 +02:00
2153 changed files with 112170 additions and 35110 deletions

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @prowler-cloud/prowler-oss
* @prowler-cloud/prowler-oss @prowler-cloud/prowler-dev

View File

@@ -1,6 +1,6 @@
name: 💡 Feature Request
description: Suggest an idea for this project
labels: ["enhancement", "status/needs-triage"]
labels: ["feature-request", "status/needs-triage"]
body:

View File

@@ -5,11 +5,38 @@
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: master
labels:
- "dependencies"
- "pip"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: master
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: v3
labels:
- "dependencies"
- "pip"
- "v3"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: v3
labels:
- "github_actions"
- "v3"

27
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
documentation:
- changed-files:
- any-glob-to-any-file: "docs/**"
provider/aws:
- changed-files:
- any-glob-to-any-file: "prowler/providers/aws/**"
- any-glob-to-any-file: "tests/providers/aws/**"
provider/azure:
- changed-files:
- any-glob-to-any-file: "prowler/providers/azure/**"
- any-glob-to-any-file: "tests/providers/azure/**"
provider/gcp:
- changed-files:
- any-glob-to-any-file: "prowler/providers/gcp/**"
- any-glob-to-any-file: "tests/providers/gcp/**"
provider/kubernetes:
- changed-files:
- any-glob-to-any-file: "prowler/providers/kubernetes/**"
- any-glob-to-any-file: "tests/providers/kubernetes/**"
github_actions:
- changed-files:
- any-glob-to-any-file: ".github/workflows/*"

View File

@@ -0,0 +1,24 @@
name: Pull Request Documentation Link
on:
pull_request:
branches:
- 'master'
- 'v3'
paths:
- 'docs/**'
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
jobs:
documentation-link:
name: Documentation Link
runs-on: ubuntu-latest
steps:
- name: Leave PR comment with the SaaS Documentation URI
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ env.PR_NUMBER }}
body: |
You can check the documentation for this PR here -> [SaaS Documentation](https://prowler-prowler-docs--${{ env.PR_NUMBER }}.com.readthedocs.build/projects/prowler-open-source/en/${{ env.PR_NUMBER }}/)

View File

@@ -3,6 +3,7 @@ name: build-lint-push-containers
on:
push:
branches:
- "v3"
- "master"
paths-ignore:
- ".github/**"
@@ -13,52 +14,98 @@ on:
types: [published]
env:
# AWS Configuration
AWS_REGION_STG: eu-west-1
AWS_REGION_PLATFORM: eu-west-1
AWS_REGION: us-east-1
# Container's configuration
IMAGE_NAME: prowler
DOCKERFILE_PATH: ./Dockerfile
# Tags
LATEST_TAG: latest
STABLE_TAG: stable
TEMPORARY_TAG: temporary
DOCKERFILE_PATH: ./Dockerfile
PYTHON_VERSION: 3.9
# The RELEASE_TAG is set during runtime in releases
RELEASE_TAG: ""
# The PROWLER_VERSION and PROWLER_VERSION_MAJOR are set during runtime in releases
PROWLER_VERSION: ""
PROWLER_VERSION_MAJOR: ""
# TEMPORARY_TAG: temporary
# Python configuration
PYTHON_VERSION: 3.12
jobs:
# Build Prowler OSS container
container-build-push:
# needs: dockerfile-linter
runs-on: ubuntu-latest
outputs:
prowler_version_major: ${{ steps.get-prowler-version.outputs.PROWLER_VERSION_MAJOR }}
prowler_version: ${{ steps.update-prowler-version.outputs.PROWLER_VERSION }}
env:
POETRY_VIRTUALENVS_CREATE: "false"
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup python (release)
if: github.event_name == 'release'
uses: actions/setup-python@v2
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install dependencies (release)
if: github.event_name == 'release'
- name: Install Poetry
run: |
pipx install poetry
pipx inject poetry poetry-bumpversion
- name: Get Prowler version
id: get-prowler-version
run: |
PROWLER_VERSION="$(poetry version -s 2>/dev/null)"
# Store prowler version major just for the release
PROWLER_VERSION_MAJOR="${PROWLER_VERSION%%.*}"
echo "PROWLER_VERSION_MAJOR=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION_MAJOR=${PROWLER_VERSION_MAJOR}" >> "${GITHUB_OUTPUT}"
case ${PROWLER_VERSION_MAJOR} in
3)
echo "LATEST_TAG=v3-latest" >> "${GITHUB_ENV}"
echo "STABLE_TAG=v3-stable" >> "${GITHUB_ENV}"
;;
4)
echo "LATEST_TAG=latest" >> "${GITHUB_ENV}"
echo "STABLE_TAG=stable" >> "${GITHUB_ENV}"
;;
*)
# Fallback if any other version is present
echo "Releasing another Prowler major version, aborting..."
exit 1
;;
esac
- name: Update Prowler version (release)
id: update-prowler-version
if: github.event_name == 'release'
run: |
poetry version ${{ github.event.release.tag_name }}
PROWLER_VERSION="${{ github.event.release.tag_name }}"
poetry version "${PROWLER_VERSION}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_ENV}"
echo "PROWLER_VERSION=${PROWLER_VERSION}" >> "${GITHUB_OUTPUT}"
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Public ECR
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: public.ecr.aws
username: ${{ secrets.PUBLIC_ECR_AWS_ACCESS_KEY_ID }}
@@ -67,11 +114,11 @@ jobs:
AWS_REGION: ${{ env.AWS_REGION }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build and push container image (latest)
if: github.event_name == 'push'
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
push: true
tags: |
@@ -83,16 +130,16 @@ jobs:
- name: Build and push container image (release)
if: github.event_name == 'release'
uses: docker/build-push-action@v2
uses: docker/build-push-action@v5
with:
# Use local context to get changes
# https://github.com/docker/build-push-action#path-context
context: .
push: true
tags: |
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
${{ secrets.DOCKER_HUB_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.PROWLER_VERSION }}
${{ secrets.PUBLIC_ECR_REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ env.STABLE_TAG }}
file: ${{ env.DOCKERFILE_PATH }}
cache-from: type=gha
@@ -102,16 +149,26 @@ jobs:
needs: container-build-push
runs-on: ubuntu-latest
steps:
- name: Get latest commit info
- name: Get latest commit info (latest)
if: github.event_name == 'push'
run: |
LATEST_COMMIT_HASH=$(echo ${{ github.event.after }} | cut -b -7)
echo "LATEST_COMMIT_HASH=${LATEST_COMMIT_HASH}" >> $GITHUB_ENV
- name: Dispatch event for latest
if: github.event_name == 'push'
- name: Dispatch event (latest)
if: github.event_name == 'push' && needs.container-build-push.outputs.prowler_version_major == '3'
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 "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"latest", "tag": "${{ env.LATEST_COMMIT_HASH }}"}}'
- name: Dispatch event for release
if: github.event_name == 'release'
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 "X-GitHub-Api-Version: 2022-11-28" \
--data '{"event_type":"dispatch","client_payload":{"version":"v3-latest", "tag": "${{ env.LATEST_COMMIT_HASH }}"}}'
- name: Dispatch event (release)
if: github.event_name == 'release' && needs.container-build-push.outputs.prowler_version_major == '3'
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 "X-GitHub-Api-Version: 2022-11-28" --data '{"event_type":"dispatch","client_payload":{"version":"release", "tag":"${{ github.event.release.tag_name }}"}}'
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 "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

@@ -13,10 +13,10 @@ name: "CodeQL"
on:
push:
branches: [ "master", prowler-2, prowler-3.0-dev ]
branches: [ "master", "v3" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
branches: [ "master", "v3" ]
schedule:
- cron: '00 12 * * *'
@@ -37,11 +37,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -52,6 +52,6 @@ jobs:
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@@ -7,12 +7,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@v3.4.4
uses: trufflesecurity/trufflehog@v3.74.0
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified

16
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: "Pull Request Labeler"
on:
pull_request_target:
branches:
- "master"
- "v3"
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5

View File

@@ -4,27 +4,31 @@ on:
push:
branches:
- "master"
- "v3"
pull_request:
branches:
- "master"
- "v3"
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Test if changes are in not ignored paths
id: are-non-ignored-files-changed
uses: tj-actions/changed-files@v39
uses: tj-actions/changed-files@v44
with:
files: ./**
files_ignore: |
.github/**
README.md
docs/**
permissions/**
mkdocs.yml
- name: Install poetry
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
run: |
@@ -32,7 +36,7 @@ jobs:
pipx install poetry
- name: Set up Python ${{ matrix.python-version }}
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "poetry"
@@ -84,6 +88,6 @@ jobs:
poetry run pytest -n auto --cov=./prowler --cov-report=xml tests
- name: Upload coverage reports to Codecov
if: steps.are-non-ignored-files-changed.outputs.any_changed == 'true'
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -6,7 +6,10 @@ on:
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
GITHUB_BRANCH: master
PYTHON_VERSION: 3.11
CACHE: "poetry"
# TODO: create a bot user for this kind of tasks, like prowler-bot
GIT_COMMITTER_EMAIL: "sergio@prowler.com"
jobs:
release-prowler-job:
@@ -15,65 +18,81 @@ jobs:
POETRY_VIRTUALENVS_CREATE: "false"
name: Release Prowler to PyPI
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
with:
ref: ${{ env.GITHUB_BRANCH }}
- name: Get Prowler version
run: |
PROWLER_VERSION="${{ env.RELEASE_TAG }}"
case ${PROWLER_VERSION%%.*} in
3)
echo "Releasing Prowler v3 with tag ${PROWLER_VERSION}"
;;
4)
echo "Releasing Prowler v4 with tag ${PROWLER_VERSION}"
;;
*)
echo "Releasing another Prowler major version, aborting..."
exit 1
;;
esac
- uses: actions/checkout@v4
- name: Install dependencies
run: |
pipx install poetry
pipx inject poetry poetry-bumpversion
- name: setup python
uses: actions/setup-python@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.9
cache: 'poetry'
- name: Change version and Build package
python-version: ${{ env.PYTHON_VERSION }}
cache: ${{ env.CACHE }}
- name: Update Poetry and config version
run: |
poetry version ${{ env.RELEASE_TAG }}
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Push updated version to the release tag
run: |
# Configure Git
git config user.name "github-actions"
git config user.email "<noreply@github.com>"
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
# Add the files with the version changed
git add prowler/config/config.py pyproject.toml
git commit -m "chore(release): ${{ env.RELEASE_TAG }}" --no-verify
git tag -fa ${{ env.RELEASE_TAG }} -m "chore(release): ${{ env.RELEASE_TAG }}"
git commit -m "chore(release): ${{ env.RELEASE_TAG }}" --no-verify -S
# Replace the tag with the version updated
git tag -fa ${{ env.RELEASE_TAG }} -m "chore(release): ${{ env.RELEASE_TAG }}" --sign
# Push the tag
git push -f origin ${{ env.RELEASE_TAG }}
- name: Build Prowler package
run: |
poetry build
- name: Publish prowler package to PyPI
- name: Publish Prowler package to PyPI
run: |
poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }}
poetry publish
# Create pull request with new version
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
token: ${{ secrets.PROWLER_ACCESS_TOKEN }}
commit-message: "chore(release): update Prowler Version to ${{ env.RELEASE_TAG }}."
branch: release-${{ env.RELEASE_TAG }}
labels: "status/waiting-for-revision, severity/low"
title: "chore(release): update Prowler Version to ${{ env.RELEASE_TAG }}"
body: |
### Description
This PR updates Prowler Version to ${{ env.RELEASE_TAG }}.
### License
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
- name: Replicate PyPi Package
- name: Replicate PyPI package
run: |
rm -rf ./dist && rm -rf ./build && rm -rf prowler.egg-info
pip install toml
python util/replicate_pypi_package.py
poetry build
- name: Publish prowler-cloud package to PyPI
run: |
poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }}
poetry publish
# Create pull request to github.com/Homebrew/homebrew-core to update prowler formula
- name: Bump Homebrew formula
uses: mislav/bump-homebrew-formula-action@v2
with:
formula-name: prowler
base-branch: release-${{ env.RELEASE_TAG }}
env:
COMMITTER_TOKEN: ${{ secrets.PROWLER_ACCESS_TOKEN }}

View File

@@ -23,12 +23,12 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ env.GITHUB_BRANCH }}
- name: setup python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.9 #install the python needed
@@ -38,7 +38,7 @@ jobs:
pip install boto3
- name: Configure AWS Credentials -- DEV
uses: aws-actions/configure-aws-credentials@v1
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ env.AWS_REGION_DEV }}
role-to-assume: ${{ secrets.DEV_IAM_ROLE_ARN }}
@@ -50,12 +50,12 @@ jobs:
# Create pull request
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.PROWLER_ACCESS_TOKEN }}
commit-message: "feat(regions_update): Update regions for AWS services."
branch: "aws-services-regions-updated-${{ github.sha }}"
labels: "status/waiting-for-revision, severity/low"
labels: "status/waiting-for-revision, severity/low, provider/aws, backport-v3"
title: "chore(regions_update): Changes in regions for AWS services."
body: |
### Description

6
.gitignore vendored
View File

@@ -9,8 +9,9 @@
__pycache__
venv/
build/
dist/
/dist/
*.egg-info/
*/__pycache__/*.pyc
# Session
Session.vim
@@ -51,3 +52,6 @@ junit-reports/
.coverage*
.coverage
coverage*
# Node
node_modules

View File

@@ -1,7 +1,7 @@
repos:
## GENERAL
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-merge-conflict
- id: check-yaml
@@ -15,7 +15,7 @@ repos:
## TOML
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.10.0
rev: v2.12.0
hooks:
- id: pretty-format-toml
args: [--autofix]
@@ -26,9 +26,10 @@ repos:
rev: v0.9.0
hooks:
- id: shellcheck
exclude: contrib
## PYTHON
- repo: https://github.com/myint/autoflake
rev: v2.2.0
rev: v2.2.1
hooks:
- id: autoflake
args:
@@ -39,25 +40,25 @@ repos:
]
- repo: https://github.com/timothycrosley/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 24.1.1
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
rev: 7.0.0
hooks:
- id: flake8
exclude: contrib
args: ["--ignore=E266,W503,E203,E501,W605"]
- repo: https://github.com/python-poetry/poetry
rev: 1.6.0 # add version here
rev: 1.7.0
hooks:
- id: poetry-check
- id: poetry-lock
@@ -80,18 +81,12 @@ repos:
- id: trufflehog
name: TruffleHog
description: Detect secrets in your data.
# entry: bash -c 'trufflehog git file://. --only-verified --fail'
entry: bash -c 'trufflehog --no-update git file://. --only-verified --fail'
# For running trufflehog in docker, use the following entry instead:
entry: bash -c 'docker run -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --only-verified --fail'
# entry: bash -c 'docker run -v "$(pwd):/workdir" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --only-verified --fail'
language: system
stages: ["commit", "push"]
- id: pytest-check
name: pytest-check
entry: bash -c 'pytest tests -n auto'
language: system
files: '.*\.py'
- id: bandit
name: bandit
description: "Bandit is a tool for finding common security issues in Python code"

View File

@@ -8,16 +8,18 @@ version: 2
build:
os: "ubuntu-22.04"
tools:
python: "3.9"
python: "3.11"
jobs:
post_create_environment:
# Install poetry
# https://python-poetry.org/docs/#installing-manually
- pip install poetry
# Tell poetry to not use a virtual environment
- poetry config virtualenvs.create false
- python -m pip install poetry
post_install:
- poetry install -E docs
# Install dependencies with 'docs' dependency group
# https://python-poetry.org/docs/managing-dependencies/#dependency-groups
# VIRTUAL_ENV needs to be set manually for now.
# See https://github.com/readthedocs/readthedocs.org/pull/11152/
- VIRTUAL_ENV=${READTHEDOCS_VIRTUALENV_PATH} python -m poetry install --only=docs
mkdocs:
configuration: mkdocs.yml

View File

@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at community@prowler.cloud. All
reported by contacting the project team at [support.prowler.com](https://customer.support.prowler.com/servicedesk/customer/portals). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.

View File

@@ -10,4 +10,4 @@
Want some swag as appreciation for your contribution?
# Prowler Developer Guide
https://docs.prowler.cloud/en/latest/tutorials/developer-guide/
https://docs.prowler.com/projects/prowler-open-source/en/latest/developer-guide/introduction/

View File

@@ -1,4 +1,4 @@
FROM python:3.11-alpine
FROM python:3.12-alpine
LABEL maintainer="https://github.com/prowler-cloud/prowler"
@@ -15,7 +15,8 @@ USER prowler
# Copy necessary files
WORKDIR /home/prowler
COPY prowler/ /home/prowler/prowler/
COPY prowler/ /home/prowler/prowler/
COPY dashboard/ /home/prowler/dashboard/
COPY pyproject.toml /home/prowler
COPY README.md /home/prowler
@@ -26,6 +27,10 @@ ENV PATH="$HOME/.local/bin:$PATH"
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir .
# Remove deprecated dash dependencies
RUN pip uninstall dash-html-components -y && \
pip uninstall dash-core-components -y
# Remove Prowler directory and build files
USER 0
RUN rm -rf /home/prowler/prowler /home/prowler/pyproject.toml /home/prowler/README.md /home/prowler/build /home/prowler/prowler.egg-info

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Netflix, Inc.
Copyright @ 2024 Toni de la Fuente
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -27,7 +27,7 @@ lint: ## Lint Code
@echo "Running black... "
black --check .
@echo "Running pylint..."
pylint --disable=W,C,R,E -j 0 providers lib util config
pylint --disable=W,C,R,E -j 0 prowler util
##@ PyPI
pypi-clean: ## Delete the distribution files

252
README.md
View File

@@ -1,24 +1,31 @@
<p align="center">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/62c1ce73bbcdd6b9e5ba03dfcae26dfd165defd9/docs/img/prowler-pro-dark.png?raw=True#gh-dark-mode-only" width="150" height="36">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/62c1ce73bbcdd6b9e5ba03dfcae26dfd165defd9/docs/img/prowler-pro-light.png?raw=True#gh-light-mode-only" width="15%" height="15%">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-black.png?raw=True#gh-light-mode-only" width="350" height="115">
<img align="center" src="https://github.com/prowler-cloud/prowler/blob/master/docs/img/prowler-logo-white.png?raw=True#gh-dark-mode-only" width="350" height="115">
</p>
<p align="center">
<b><i>See all the things you and your team can do with ProwlerPro at <a href="https://prowler.pro">prowler.pro</a></i></b>
<b><i>Prowler SaaS </b> and <b>Prowler Open Source</b> are as dynamic and adaptable as the environment theyre meant to protect. Trusted by the leaders in security.
</p>
<p align="center">
<b>Learn more at <a href="https://prowler.com">prowler.com</i></b>
</p>
<p align="center">
<a href="https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog"><img width="30" height="30" alt="Prowler community on Slack" src="https://github.com/prowler-cloud/prowler/assets/3985464/3617e470-670c-47c9-9794-ce895ebdb627"></a>
<br>
<a href="https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog">Join our Prowler community!</a>
</p>
<hr>
<p align="center">
<img src="https://user-images.githubusercontent.com/3985464/113734260-7ba06900-96fb-11eb-82bc-d4f68a1e2710.png" />
</p>
<p align="center">
<a href="https://join.slack.com/t/prowler-workspace/shared_invite/zt-1hix76xsl-2uq222JIXrC7Q8It~9ZNog"><img alt="Slack Shield" src="https://img.shields.io/badge/slack-prowler-brightgreen.svg?logo=slack"></a>
<a href="https://pypi.org/project/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/v/prowler.svg"></a>
<a href="https://pypi.python.org/pypi/prowler/"><img alt="Python Version" src="https://img.shields.io/pypi/pyversions/prowler.svg"></a>
<a href="https://pypistats.org/packages/prowler"><img alt="PyPI Prowler Downloads" src="https://img.shields.io/pypi/dw/prowler.svg?label=prowler%20downloads"></a>
<a href="https://pypistats.org/packages/prowler-cloud"><img alt="PyPI Prowler-Cloud Downloads" src="https://img.shields.io/pypi/dw/prowler-cloud.svg?label=prowler-cloud%20downloads"></a>
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/toniblyx/prowler"></a>
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker" src="https://img.shields.io/docker/cloud/build/toniblyx/prowler"></a>
<a href="https://hub.docker.com/r/toniblyx/prowler"><img alt="Docker" src="https://img.shields.io/docker/image-size/toniblyx/prowler"></a>
<a href="https://gallery.ecr.aws/prowler-cloud/prowler"><img width="120" height=19" alt="AWS ECR Gallery" src="https://user-images.githubusercontent.com/3985464/151531396-b6535a68-c907-44eb-95a1-a09508178616.png"></a>
<a href="https://codecov.io/gh/prowler-cloud/prowler"><img src="https://codecov.io/gh/prowler-cloud/prowler/graph/badge.svg?token=OflBGsdpDl"/></a>
</p>
<p align="center">
<a href="https://github.com/prowler-cloud/prowler"><img alt="Repo size" src="https://img.shields.io/github/repo-size/prowler-cloud/prowler"></a>
@@ -30,45 +37,55 @@
<a href="https://twitter.com/ToniBlyx"><img alt="Twitter" src="https://img.shields.io/twitter/follow/toniblyx?style=social"></a>
<a href="https://twitter.com/prowlercloud"><img alt="Twitter" src="https://img.shields.io/twitter/follow/prowlercloud?style=social"></a>
</p>
<hr>
# Description
`Prowler` is an Open Source security tool to perform AWS, GCP and Azure security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness.
**Prowler** is an Open Source security tool to perform AWS, Azure, Google Cloud and Kubernetes security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness, and also remediations! We have Prowler CLI (Command Line Interface) that we call Prowler Open Source and a service on top of it that we call <a href="https://prowler.com">Prowler SaaS</a>.
## Prowler CLI
```console
prowler <provider>
```
![Prowler CLI Execution](docs/img/short-display.png)
## Prowler Dashboard
```console
prowler dashboard
```
![Prowler Dashboard](docs/img/dashboard.png)
It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, AWS Well-Architected Framework Security Pillar, AWS Foundational Technical Review (FTR), ENS (Spanish National Security Scheme) and your custom security frameworks.
| Provider | Checks | Services | [Compliance Frameworks](https://docs.prowler.cloud/en/latest/tutorials/compliance/) | [Categories](https://docs.prowler.cloud/en/latest/tutorials/misc/#categories) |
| 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 | 290 | 56 -> `prowler aws --list-services` | 25 -> `prowler aws --list-compliance` | 5 -> `prowler aws --list-categories` |
| GCP | 73 | 11 -> `prowler gcp --list-services` | 1 -> `prowler gcp --list-compliance` | 2 -> `prowler gcp --list-categories`|
| Azure | 23 | 4 -> `prowler azure --list-services` | CIS soon | 1 -> `prowler azure --list-categories` |
| Kubernetes | Planned | - | - | - |
| AWS | 304 | 61 -> `prowler aws --list-services` | 28 -> `prowler aws --list-compliance` | 6 -> `prowler aws --list-categories` |
| GCP | 75 | 11 -> `prowler gcp --list-services` | 1 -> `prowler gcp --list-compliance` | 2 -> `prowler gcp --list-categories`|
| Azure | 127 | 16 -> `prowler azure --list-services` | 2 -> `prowler azure --list-compliance` | 2 -> `prowler azure --list-categories` |
| Kubernetes | 83 | 7 -> `prowler kubernetes --list-services` | 1 -> `prowler kubernetes --list-compliance` | 7 -> `prowler kubernetes --list-categories` |
# 📖 Documentation
The full documentation can now be found at [https://docs.prowler.cloud](https://docs.prowler.cloud)
## Looking for Prowler v2 documentation?
For Prowler v2 Documentation, please go to https://github.com/prowler-cloud/prowler/tree/2.12.1.
# ⚙️ Install
# 💻 Installation
## Pip package
Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-cloud/), thus can be installed using pip with Python >= 3.9:
Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-cloud/), thus can be installed using pip with Python >= 3.9, < 3.13:
```console
pip install prowler
prowler -v
```
More details at https://docs.prowler.cloud
More details at [https://docs.prowler.com](https://docs.prowler.com/projects/prowler-open-source/en/latest/)
## Containers
The available versions of Prowler are the following:
- `latest`: in sync with master branch (bear in mind that it is not a stable version)
- `latest`: in sync with `master` branch (bear in mind that it is not a stable version)
- `v3-latest`: in sync with `v3` branch (bear in mind that it is not a stable version)
- `<x.y.z>` (release): you can find the releases [here](https://github.com/prowler-cloud/prowler/releases), those are stable releases.
- `stable`: this tag always point to the latest release.
- `v3-stable`: this tag always point to the latest release for v3.
The container images are available here:
@@ -77,7 +94,7 @@ The container images are available here:
## From Github
Python >= 3.9 is required with pip and poetry:
Python >= 3.9, < 3.13 is required with pip and poetry:
```
git clone https://github.com/prowler-cloud/prowler
@@ -89,185 +106,30 @@ python prowler.py -v
# 📐✏️ High level architecture
You can run Prowler from your workstation, an EC2 instance, Fargate or any other container, Codebuild, CloudShell and Cloud9.
You can run Prowler from your workstation, a Kubernetes Job, a Google Compute Engine, an Azure VM, an EC2 instance, Fargate or any other container, CloudShell and many more.
![Architecture](https://github.com/prowler-cloud/prowler/assets/38561120/080261d9-773d-4af1-af79-217a273e3176)
![Architecture](docs/img/architecture.png)
# 📝 Requirements
# Deprecations from v3
Prowler has been written in Python using the [AWS SDK (Boto3)](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html#), [Azure SDK](https://azure.github.io/azure-sdk-for-python/) and [GCP API Python Client](https://github.com/googleapis/google-api-python-client/).
## AWS
## General
- `Allowlist` now is called `Mutelist`.
- The `--quiet` option has been deprecated, now use the `--status` flag to select the finding's status you want to get from PASS, FAIL or MANUAL.
- All `INFO` finding's status has changed to `MANUAL`.
- The CSV output format is common for all the providers.
Since Prowler uses AWS Credentials under the hood, you can follow any authentication method as described [here](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-precedence).
Make sure you have properly configured your AWS-CLI with a valid Access Key and Region or declare AWS variables properly (or instance profile/role):
```console
aws configure
```
or
```console
export AWS_ACCESS_KEY_ID="ASXXXXXXX"
export AWS_SECRET_ACCESS_KEY="XXXXXXXXX"
export AWS_SESSION_TOKEN="XXXXXXXXX"
```
Those credentials must be associated to a user or role with proper permissions to do all checks. To make sure, add the following AWS managed policies to the user or role being used:
- `arn:aws:iam::aws:policy/SecurityAudit`
- `arn:aws:iam::aws:policy/job-function/ViewOnlyAccess`
> Moreover, some read-only additional permissions are needed for several checks, make sure you attach also the custom policy [prowler-additions-policy.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-additions-policy.json) to the role you are using.
> If you want Prowler to send findings to [AWS Security Hub](https://aws.amazon.com/security-hub), make sure you also attach the custom policy [prowler-security-hub.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-security-hub.json).
## Azure
Prowler for Azure supports the following authentication types:
- Service principal authentication by environment variables (Enterprise Application)
- Current az cli credentials stored
- Interactive browser authentication
- Managed identity authentication
### Service Principal authentication
To allow Prowler assume the service principal identity to start the scan, it is needed to configure the following environment variables:
```console
export AZURE_CLIENT_ID="XXXXXXXXX"
export AZURE_TENANT_ID="XXXXXXXXX"
export AZURE_CLIENT_SECRET="XXXXXXX"
```
If you try to execute Prowler with the `--sp-env-auth` flag and those variables are empty or not exported, the execution is going to fail.
### AZ CLI / Browser / Managed Identity authentication
The other three cases do not need additional configuration, `--az-cli-auth` and `--managed-identity-auth` are automated options, `--browser-auth` needs the user to authenticate using the default browser to start the scan. Also `--browser-auth` needs the tenant id to be specified with `--tenant-id`.
### Permissions
To use each one, you need to pass the proper flag to the execution. Prowler for Azure handles two types of permission scopes, which are:
- **Azure Active Directory permissions**: Used to retrieve metadata from the identity assumed by Prowler and future AAD checks (not mandatory to have access to execute the tool)
- **Subscription scope permissions**: Required to launch the checks against your resources, mandatory to launch the tool.
#### Azure Active Directory scope
Azure Active Directory (AAD) permissions required by the tool are the following:
- `Directory.Read.All`
- `Policy.Read.All`
#### Subscriptions scope
Regarding the subscription scope, Prowler by default scans all the subscriptions that is able to list, so it is required to add the following RBAC builtin roles per subscription to the entity that is going to be assumed by the tool:
- `Security Reader`
- `Reader`
## Google Cloud Platform
Prowler will follow the same credentials search as [Google authentication libraries](https://cloud.google.com/docs/authentication/application-default-credentials#search_order):
1. [GOOGLE_APPLICATION_CREDENTIALS environment variable](https://cloud.google.com/docs/authentication/application-default-credentials#GAC)
2. [User credentials set up by using the Google Cloud CLI](https://cloud.google.com/docs/authentication/application-default-credentials#personal)
3. [The attached service account, returned by the metadata server](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa)
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the following roles to the member associated with the credentials:
- Viewer
- Security Reviewer
- Stackdriver Account Viewer
> By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.
# 💻 Basic Usage
To run prowler, you will need to specify the provider (e.g aws or azure):
```console
prowler <provider>
```
![Prowler Execution](https://github.com/prowler-cloud/prowler/blob/b91b0103ff38e66a915c8a0ed84905a07e4aae1d/docs/img/short-display.png?raw=True)
> Running the `prowler` command without options will use your environment variable credentials.
By default, prowler will generate a CSV, a JSON and a HTML report, however you can generate JSON-ASFF (only for AWS Security Hub) report with `-M` or `--output-modes`:
```console
prowler <provider> -M csv json json-asff html
```
The html report will be located in the `output` directory as the other files and it will look like:
![Prowler Execution](https://github.com/prowler-cloud/prowler/blob/62c1ce73bbcdd6b9e5ba03dfcae26dfd165defd9/docs/img/html-output.png?raw=True)
You can use `-l`/`--list-checks` or `--list-services` to list all available checks or services within the provider.
```console
prowler <provider> --list-checks
prowler <provider> --list-services
```
For executing specific checks or services you can use options `-c`/`--checks` or `-s`/`--services`:
```console
prowler aws --checks s3_bucket_public_access
prowler aws --services s3 ec2
```
Also, checks and services can be excluded with options `-e`/`--excluded-checks` or `--excluded-services`:
```console
prowler aws --excluded-checks s3_bucket_public_access
prowler aws --excluded-services s3 ec2
```
You can always use `-h`/`--help` to access to the usage information and all the possible options:
```console
prowler -h
```
## Checks Configurations
Several Prowler's checks have user configurable variables that can be modified in a common **configuration file**.
This file can be found in the following path:
```
prowler/config/config.yaml
```
We have deprecated some of our outputs formats:
- The HTML is replaced for the new Prowler Dashboard, run `prowler dashboard`.
- The native JSON is replaced for the JSON [OCSF](https://schema.ocsf.io/) v1.1.0, common for all the providers.
## AWS
- Deprecate the AWS flag --sts-endpoint-region since we use AWS STS regional tokens.
- To send only FAILS to AWS Security Hub, now use either `--send-sh-only-fails` or `--security-hub --status FAIL`.
Use a custom AWS profile with `-p`/`--profile` and/or AWS regions which you want to audit with `-f`/`--filter-region`:
```console
prowler aws --profile custom-profile -f us-east-1 eu-south-2
```
> By default, `prowler` will scan all AWS regions.
# 📖 Documentation
## Azure
With Azure you need to specify which auth method is going to be used:
```console
prowler azure [--sp-env-auth, --az-cli-auth, --browser-auth, --managed-identity-auth]
```
> By default, `prowler` will scan all Azure subscriptions.
## Google Cloud Platform
Optionally, you can provide the location of an application credential JSON file with the following argument:
```console
prowler gcp --credentials-file path
```
> By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.
Install, Usage, Tutorials and Developer Guide is at https://docs.prowler.com/
# 📃 License

View File

@@ -14,7 +14,7 @@ As an **AWS Partner** and we have passed the [AWS Foundation Technical Review (F
If you would like to report a vulnerability or have a security concern regarding Prowler Open Source or ProwlerPro service, please submit the information by contacting to help@prowler.pro.
The information you share with Verica as part of this process is kept confidential within Verica and the Prowler team. We will only share this information with a third party if the vulnerability you report is found to affect a third-party product, in which case we will share this information with the third-party product's author or manufacturer. Otherwise, we will only share this information as permitted by you.
The information you share with ProwlerPro as part of this process is kept confidential within ProwlerPro. We will only share this information with a third party if the vulnerability you report is found to affect a third-party product, in which case we will share this information with the third-party product's author or manufacturer. Otherwise, we will only share this information as permitted by you.
We will review the submitted report, and assign it a tracking number. We will then respond to you, acknowledging receipt of the report, and outline the next steps in the process.

View File

@@ -1,17 +1,8 @@
#!/bin/bash
# Install system dependencies
sudo yum -y install openssl-devel bzip2-devel libffi-devel gcc
# Upgrade to Python 3.9
cd /tmp && wget https://www.python.org/ftp/python/3.9.13/Python-3.9.13.tgz
tar zxf Python-3.9.13.tgz
cd Python-3.9.13/ || exit
./configure --enable-optimizations
sudo make altinstall
python3.9 --version
# Install Prowler
cd ~ || exit
python3.9 -m pip install prowler-cloud
prowler -v
# Run Prowler
prowler
sudo bash
adduser prowler
su prowler
pip install prowler
cd /tmp
prowler aws

2
dashboard/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
DASHBOARD_PORT = 11666
DASHBOARD_ARGS = {"debug": True, "port": DASHBOARD_PORT, "use_reloader": False}

176
dashboard/__main__.py Normal file
View File

@@ -0,0 +1,176 @@
# Importing Packages
import sys
import warnings
import click
import dash
import dash_bootstrap_components as dbc
from colorama import Fore, Style
from dash import dcc, html
from dash.dependencies import Input, Output
from dashboard.config import folder_path_overview
from prowler.config.config import orange_color
from prowler.lib.banner import print_banner
warnings.filterwarnings("ignore")
cli = sys.modules["flask.cli"]
print_banner(verbose=False)
print(
f"{Fore.GREEN}Loading all CSV files from the folder {folder_path_overview} ...\n{Style.RESET_ALL}"
)
cli.show_server_banner = lambda *x: click.echo(
f"{Fore.YELLOW}NOTE:{Style.RESET_ALL} If you are a {Fore.GREEN}{Style.BRIGHT}Prowler SaaS{Style.RESET_ALL} customer and you want to use your data from your S3 bucket,\nrun: `{orange_color}aws s3 cp s3://<your-bucket>/output/csv ./output --recursive{Style.RESET_ALL}`\nand then run `prowler dashboard` again to load the new files."
)
# Initialize the app - incorporate css
dashboard = dash.Dash(
__name__,
external_stylesheets=[dbc.themes.DARKLY],
use_pages=True,
suppress_callback_exceptions=True,
title="Prowler Dashboard",
)
# Logo
prowler_logo = html.Img(
src="https://prowler.com/wp-content/uploads/logo-dashboard.png", alt="Prowler Logo"
)
menu_icons = {
"overview": "/assets/images/icons/overview.svg",
"compliance": "/assets/images/icons/compliance.svg",
}
# Function to generate navigation links
def generate_nav_links(current_path):
nav_links = []
for page in dash.page_registry.values():
# Gets the icon URL based on the page name
icon_url = menu_icons.get(page["name"].lower())
is_active = (
" bg-prowler-stone-950 border-r-4 border-solid border-prowler-lime"
if current_path == page["relative_path"]
else ""
)
link_class = f"block hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime{is_active}"
link_content = html.Span(
[
html.Img(src=icon_url, className="w-5"),
html.Span(page["name"], className="font-medium text-base leading-6"),
],
className="flex justify-center lg:justify-normal items-center gap-x-3 py-2 px-3",
)
nav_link = html.Li(
dcc.Link(link_content, href=page["relative_path"], className=link_class)
)
nav_links.append(nav_link)
return nav_links
def generate_help_menu():
help_links = [
{
"title": "Help",
"url": "https://github.com/prowler-cloud/prowler/issues",
"icon": "/assets/images/icons/help.png",
},
{
"title": "Docs",
"url": "https://docs.prowler.com",
"icon": "/assets/images/icons/docs.png",
},
]
link_class = "block hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime"
menu_items = []
for link in help_links:
menu_item = html.Li(
html.A(
html.Span(
[
html.Img(src=link["icon"], className="w-5"),
html.Span(
link["title"], className="font-medium text-base leading-6"
),
],
className="flex items-center gap-x-3 py-2 px-3",
),
href=link["url"],
target="_blank",
className=link_class,
)
)
menu_items.append(menu_item)
return menu_items
# Layout
dashboard.layout = html.Div(
[
dcc.Location(id="url", refresh=False),
html.Link(rel="icon", href="assets/favicon.ico"),
# Placeholder for dynamic navigation bar
html.Div(
[
html.Div(
id="navigation-bar", className="bg-prowler-stone-900 min-w-36 z-10"
),
html.Div(
[
dash.page_container,
],
id="content_select",
className="bg-prowler-white w-full col-span-11 h-screen mx-auto overflow-y-scroll no-scrollbar px-10 py-7",
),
],
className="grid custom-grid 2xl:custom-grid-large h-screen",
),
],
className="h-screen mx-auto",
)
# Callback to update navigation bar
@dashboard.callback(Output("navigation-bar", "children"), [Input("url", "pathname")])
def update_nav_bar(pathname):
return html.Div(
[
html.Div([prowler_logo], className="mb-8 px-3"),
html.H6(
"Dashboards",
className="px-3 text-prowler-stone-500 text-sm opacity-90 font-regular mb-2",
),
html.Nav(
[html.Ul(generate_nav_links(pathname), className="")],
className="flex flex-col gap-y-6",
),
html.Nav(
[
html.A(
[
html.Span(
[
html.Img(src="assets/favicon.ico", className="w-5"),
"Subscribe to prowler SaaS",
],
className="flex items-center gap-x-3",
),
],
href="https://prowler.com/",
target="_blank",
className="block p-3 uppercase text-xs hover:bg-prowler-stone-950 hover:border-r-4 hover:border-solid hover:border-prowler-lime",
),
html.Ul(generate_help_menu(), className=""),
],
className="flex flex-col gap-y-6 mt-auto",
),
],
className="flex flex-col bg-prowler-stone-900 py-7 h-full",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFF" aria-hidden="true" class="h-5 w-5" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M9 1.5H5.625c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5zm6.61 10.936a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 14.47a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25z" clip-rule="evenodd"/>
<path d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963z"/>
</svg>

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#FFF" aria-hidden="true" class="h-5 w-5" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M2.25 13.5a8.25 8.25 0 0 1 8.25-8.25.75.75 0 0 1 .75.75v6.75H18a.75.75 0 0 1 .75.75 8.25 8.25 0 0 1-16.5 0z" clip-rule="evenodd"/>
<path fill-rule="evenodd" d="M12.75 3a.75.75 0 0 1 .75-.75 8.25 8.25 0 0 1 8.25 8.25.75.75 0 0 1-.75.75h-7.5a.75.75 0 0 1-.75-.75V3z" clip-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
dashboard/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

1301
dashboard/assets/styles/dist/output.css vendored Normal file

File diff suppressed because it is too large Load Diff

2221
dashboard/common_methods.py Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ATTRIBUTES_NAME",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_format2(
aux, "REQUIREMENTS_ATTRIBUTES_NAME", "REQUIREMENTS_ATTRIBUTES_SECTION"
)

View File

@@ -0,0 +1,23 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ATTRIBUTES_NAME",
"REQUIREMENTS_ATTRIBUTES_SECTION",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_format2(
aux, "REQUIREMENTS_ATTRIBUTES_SECTION", "REQUIREMENTS_ATTRIBUTES_NAME"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,29 @@
import warnings
from dashboard.common_methods import get_section_containers_ens
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ATTRIBUTES_MARCO",
"REQUIREMENTS_ATTRIBUTES_CATEGORIA",
"REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL",
"REQUIREMENTS_ATTRIBUTES_TIPO",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_ens(
aux,
"REQUIREMENTS_ATTRIBUTES_MARCO",
"REQUIREMENTS_ATTRIBUTES_CATEGORIA",
"REQUIREMENTS_ATTRIBUTES_IDGRUPOCONTROL",
"REQUIREMENTS_ATTRIBUTES_TIPO",
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
import warnings
from dashboard.common_methods import get_section_container_iso
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ATTRIBUTES_CATEGORY",
"REQUIREMENTS_ATTRIBUTES_OBJETIVE_ID",
"REQUIREMENTS_ATTRIBUTES_OBJETIVE_NAME",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_container_iso(
aux, "REQUIREMENTS_ATTRIBUTES_CATEGORY", "REQUIREMENTS_ATTRIBUTES_OBJETIVE_ID"
)

View File

@@ -0,0 +1,23 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_SUBTECHNIQUES",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format2(
aux, "REQUIREMENTS_ID", "REQUIREMENTS_SUBTECHNIQUES"
)

View File

@@ -0,0 +1,23 @@
import warnings
from dashboard.common_methods import get_section_containers_format2
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"REQUIREMENTS_SUBTECHNIQUES",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
].copy()
return get_section_containers_format2(
aux, "REQUIREMENTS_ID", "REQUIREMENTS_SUBTECHNIQUES"
)

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
import warnings
from dashboard.common_methods import get_section_containers_pci
warnings.filterwarnings("ignore")
def get_table(data):
aux = data[
[
"REQUIREMENTS_ID",
"CHECKID",
"STATUS",
"REGION",
"ACCOUNTID",
"RESOURCEID",
]
]
return get_section_containers_pci(aux, "REQUIREMENTS_ID")

View File

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

View File

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

32
dashboard/config.py Normal file
View File

@@ -0,0 +1,32 @@
import os
# Emojis to be used in the compliance table
pass_emoji = ""
fail_emoji = ""
info_emoji = ""
manual_emoji = "✋🏽"
# Main colors
fail_color = "#e67272"
pass_color = "#54d283"
info_color = "#2684FF"
manual_color = "#636c78"
# Muted colors
muted_fail_color = "#fca903"
muted_pass_color = "#03fccf"
muted_manual_color = "#b33696"
# Severity colors
critical_color = "#951649"
high_color = "#e11d48"
medium_color = "#ee6f15"
low_color = "#f9f5e6"
informational_color = "#3274d9"
# Folder output path
folder_path_overview = os.getcwd() + "/output"
folder_path_compliance = os.getcwd() + "/output/compliance"
# Encoding
encoding_format = "utf-8"

View File

@@ -0,0 +1,5 @@
def init_dashboard_parser(self):
"""Init the Dashboard CLI parser"""
# If we don't set `help="Dashboard"` this won't be rendered
# We don't want the dashboard to inherit from the common providers parser since it's a different component
self.subparsers.add_parser("dashboard")

157
dashboard/lib/cards.py Normal file
View File

@@ -0,0 +1,157 @@
from typing import List
from dash import html
def create_provider_card(
provider: str, provider_logo: str, account_type: str, filtered_data
) -> List[html.Div]:
"""
Card to display the provider's name and icon.
Args:
provider (str): Name of the provider.
provider_icon (str): Icon of the provider.
Returns:
html.Div: Card to display the provider's name and icon.
"""
accounts = len(
filtered_data[filtered_data["PROVIDER"] == provider]["ACCOUNT_UID"].unique()
)
checks_executed = len(
filtered_data[filtered_data["PROVIDER"] == provider]["CHECK_ID"].unique()
)
fails = len(
filtered_data[
(filtered_data["PROVIDER"] == provider)
& (filtered_data["STATUS"] == "FAIL")
]
)
passes = len(
filtered_data[
(filtered_data["PROVIDER"] == provider)
& (filtered_data["STATUS"] == "PASS")
]
)
# Take the values in the MUTED colum that are true for the provider
if "MUTED" in filtered_data.columns:
muted = len(
filtered_data[
(filtered_data["PROVIDER"] == provider)
& (filtered_data["MUTED"] == "True")
]
)
else:
muted = 0
return [
html.Div(
[
html.Div(
[
html.Div(
[
html.Div(
[
html.Div([provider_logo], className="w-8"),
],
className="p-2 shadow-box-up rounded-full",
),
html.H5(
f"{provider.upper()} {account_type}",
className="text-base font-semibold leading-snug tracking-normal text-gray-900",
),
],
className="flex justify-between items-center mb-3",
),
html.Div(
[
html.Div(
[
html.Span(
account_type,
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
accounts,
className="inline-block text-xs text-prowler-stone-900 font-bold shadow-box-down px-4 py-1 rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"Checks",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
checks_executed,
className="inline-block text-xs text-prowler-stone-900 font-bold shadow-box-down px-4 py-1 rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"FAILED",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
[
html.Div(
fails,
className="m-[2px] px-4 py-1 rounded-lg bg-gradient-failed",
),
],
className="inline-block text-xs font-bold shadow-box-down rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"PASSED",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
[
html.Div(
passes,
className="m-[2px] px-4 py-1 rounded-lg bg-gradient-passed",
),
],
className="inline-block text-xs font-bold shadow-box-down rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
html.Div(
[
html.Span(
"MUTED",
className="text-prowler-stone-900 inline-block text-3xs font-bold uppercase transition-all rounded-lg text-prowler-stone-900 shadow-box-up px-4 py-1 text-center col-span-6 flex justify-center items-center",
),
html.Div(
[
html.Div(
muted,
className="m-[2px] px-4 py-1 rounded-lg bg-gradient-muted",
),
],
className="inline-block text-xs font-bold shadow-box-down rounded-lg text-center col-span-5 col-end-13",
),
],
className="grid grid-cols-12",
),
],
className="grid gap-x-8 gap-y-4",
),
],
className="px-4 py-3",
),
],
className="relative flex flex-col bg-white shadow-provider rounded-xl w-full transition ease-in-out delay-100 hover:-translate-y-1 hover:scale-110 hover:z-50 hover:cursor-pointer",
)
]

289
dashboard/lib/dropdowns.py Normal file
View File

@@ -0,0 +1,289 @@
from dash import dcc, html
def create_date_dropdown(assesment_times: list) -> html.Div:
"""
Dropdown to select the date of the last available scan for each account.
Args:
assesment_times (list): List of dates of the last available scan for each account.
Returns:
html.Div: Dropdown to select the date of the last available scan for each account.
"""
return html.Div(
[
html.Div(
[
html.Label(
"Assessment date (last available scan) ",
className="text-prowler-stone-900 font-bold text-sm",
),
html.Img(
id="info-file-over",
src="/assets/images/icons/help-black.png",
className="w-5",
title="The date of the last available scan for each account is displayed here. If you have not run prowler yet, the date will be empty.",
),
],
style={"display": "inline-flex"},
),
dcc.Dropdown(
id="report-date-filter",
options=[
{"label": account, "value": account} for account in assesment_times
],
value=assesment_times[0],
clearable=False,
multi=False,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_date_dropdown_compliance(assesment_times: list) -> html.Div:
"""
Dropdown to select the date of the last available scan for each account.
Args:
assesment_times (list): List of dates of the last available scan for each account.
Returns:
html.Div: Dropdown to select the date of the last available scan for each account.
"""
return html.Div(
[
html.Label(
"Assesment Date:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="date-filter-analytics",
options=[
{"label": account, "value": account} for account in assesment_times
],
value=assesment_times[0],
clearable=False,
multi=False,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_region_dropdown(regions: list) -> html.Div:
"""
Dropdown to select the region of the account.
Args:
regions (list): List of regions of the account.
Returns:
html.Div: Dropdown to select the region of the account.
"""
return html.Div(
[
html.Label(
"Region / Location / Namespace :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="region-filter",
options=[{"label": region, "value": region} for region in regions],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_region_dropdown_compliance(regions: list) -> html.Div:
"""
Dropdown to select the region of the account.
Args:
regions (list): List of regions of the account.
Returns:
html.Div: Dropdown to select the region of the account.
"""
return html.Div(
[
html.Label(
"Region / Location / Namespace :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="region-filter-compliance",
options=[{"label": region, "value": region} for region in regions],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_account_dropdown(accounts: list) -> html.Div:
"""
Dropdown to select the account.
Args:
accounts (list): List of accounts.
Returns:
html.Div: Dropdown to select the account.
"""
return html.Div(
[
html.Label(
"Account / Subscription / Project / Cluster :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="cloud-account-filter",
options=[{"label": account, "value": account} for account in accounts],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_account_dropdown_compliance(accounts: list) -> html.Div:
"""
Dropdown to select the account.
Args:
accounts (list): List of accounts.
Returns:
html.Div: Dropdown to select the account.
"""
return html.Div(
[
html.Label(
"Account / Subscription / Project / Cluster :",
className="text-prowler-stone-900 font-bold text-sm",
),
dcc.Dropdown(
id="cloud-account-filter-compliance",
options=[{"label": account, "value": account} for account in accounts],
value=["All"], # Initial selection is ALL
clearable=False,
multi=True,
style={"color": "#000000", "width": "100%"},
),
],
)
def create_compliance_dropdown(compliance: list) -> html.Div:
"""
Dropdown to select the compliance.
Args:
compliance (list): List of compliance.
Returns:
html.Div: Dropdown to select the compliance.
"""
return html.Div(
[
html.Label(
"Compliance:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="report-compliance-filter",
options=[{"label": i, "value": i} for i in compliance],
value=compliance[0],
clearable=False,
style={"color": "#000000"},
),
],
)
def create_severity_dropdown(severity: list) -> html.Div:
"""
Dropdown to select the severity.
Args:
severity (list): List of severity.
Returns:
html.Div: Dropdown to select the severity.
"""
return html.Div(
[
html.Label(
"Severity:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="severity-filter",
options=[{"label": i, "value": i} for i in severity],
value=["All"],
clearable=False,
multi=True,
style={"color": "#000000"},
),
],
)
def create_service_dropdown(services: list) -> html.Div:
"""
Dropdown to select the service.
Args:
services (list): List of services.
Returns:
html.Div: Dropdown to select the service.
"""
return html.Div(
[
html.Label(
"Service:", className="text-prowler-stone-900 font-bold text-sm"
),
dcc.Dropdown(
id="service-filter",
options=[{"label": i, "value": i} for i in services],
value=["All"],
clearable=False,
multi=True,
style={"color": "#000000"},
),
],
)
def create_status_dropdown(status: list) -> html.Div:
"""
Dropdown to select the status.
Args:
status (list): List of status.
Returns:
html.Div: Dropdown to select the status.
"""
return html.Div(
[
html.Label("Status:", className="text-prowler-stone-900 font-bold text-sm"),
dcc.Dropdown(
id="status-filter",
options=[{"label": i, "value": i} for i in status],
value=["All"],
clearable=False,
multi=True,
style={"color": "#000000"},
),
],
)
def create_table_row_dropdown(table_rows: list) -> html.Div:
"""
Dropdown to select the number of rows in the table.
Args:
table_rows (list): List of number of rows.
Returns:
html.Div: Dropdown to select the number of rows in the table.
"""
return html.Div(
[
dcc.Dropdown(
id="table-rows",
options=[{"label": i, "value": i} for i in table_rows],
value=table_rows[0],
clearable=False,
style={"color": "#000000", "margin-right": "10px"},
),
],
)

174
dashboard/lib/layouts.py Normal file
View File

@@ -0,0 +1,174 @@
from dash import dcc, html
def create_layout_overview(
account_dropdown: html.Div,
date_dropdown: html.Div,
region_dropdown: html.Div,
download_button_csv: html.Button,
download_button_xlsx: html.Button,
severity_dropdown: html.Div,
service_dropdown: html.Div,
table_row_dropdown: html.Div,
status_dropdown: html.Div,
) -> html.Div:
"""
Create the layout of the dashboard.
Args:
account_dropdown (html.Div): Dropdown to select the account.
date_dropdown (html.Div): Dropdown to select the date of the last available scan for each account.
region_dropdown (html.Div): Dropdown to select the region of the account.
Returns:
html.Div: Layout of the dashboard.
"""
return html.Div(
[
dcc.Location(id="url", refresh=False),
html.Div(
[
html.H1(
"Scan Overview",
className="text-prowler-stone-900 text-2xxl font-bold",
),
html.Div(className="d-flex flex-wrap", id="subscribe_card"),
],
className="flex justify-between border-b border-prowler-500 pb-3",
),
html.Div(
[
html.Div([date_dropdown], className=""),
html.Div([account_dropdown], className=""),
html.Div([region_dropdown], className=""),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3 lg:gap-y-0",
),
html.Div(
[
html.Div([severity_dropdown], className=""),
html.Div([service_dropdown], className=""),
html.Div([status_dropdown], className=""),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-3 lg:gap-y-0",
),
html.Div(
[
html.Div(className="flex", id="aws_card", n_clicks=0),
html.Div(className="flex", id="azure_card", n_clicks=0),
html.Div(className="flex", id="gcp_card", n_clicks=0),
html.Div(className="flex", id="k8s_card", n_clicks=0),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-4 lg:gap-y-0",
),
html.H4(
"Count of Findings by severity",
className="text-prowler-stone-900 text-lg font-bold",
),
html.Div(
[
html.Div(
className="flex flex-col col-span-12 sm:col-span-6 lg:col-span-3 gap-y-4",
id="status_graph",
),
html.Div(
className="flex flex-col col-span-12 sm:col-span-6 lg:col-span-3 gap-y-4",
id="two_pie_chart",
),
html.Div(
className="flex flex-col col-span-12 sm:col-span-6 lg:col-span-6 col-end-13 gap-y-4",
id="line_plot",
),
],
className="grid gap-x-4 gap-y-4 grid-cols-12 lg:gap-y-0",
),
html.Div(
[
html.H4(
"Top Findings by Severity",
className="text-prowler-stone-900 text-lg font-bold",
),
html.Div(
[
(
html.Label(
"Table Rows:",
className="text-prowler-stone-900 font-bold text-sm",
style={"margin-right": "10px"},
)
),
table_row_dropdown,
download_button_csv,
download_button_xlsx,
],
className="flex justify-between items-center",
),
dcc.Download(id="download-data"),
],
className="flex justify-between items-center",
),
html.Div(id="table", className="grid"),
],
className="grid gap-x-8 gap-y-8 2xl:container mx-auto",
)
def create_layout_compliance(
account_dropdown: html.Div,
date_dropdown: html.Div,
region_dropdown: html.Div,
compliance_dropdown: html.Div,
) -> html.Div:
return html.Div(
[
dcc.Location(id="url", refresh=False),
html.Div(
[
html.H1(
"Compliance",
className="text-prowler-stone-900 text-2xxl font-bold",
),
html.A(
[
html.Img(src="assets/favicon.ico", className="w-5 mr-3"),
html.Span("Subscribe to prowler SaaS"),
],
href="https://prowler.pro/",
target="_blank",
className="text-prowler-stone-900 inline-flex px-4 py-2 text-xs font-bold uppercase transition-all rounded-lg text-gray-900 hover:bg-prowler-stone-900/10 border-solid border-1 hover:border-prowler-stone-900/10 hover:border-solid hover:border-1 border-prowler-stone-900/10",
),
],
className="flex justify-between border-b border-prowler-500 pb-3",
),
html.Div(
[
html.Div([date_dropdown], className=""),
html.Div([account_dropdown], className=""),
html.Div([region_dropdown], className=""),
html.Div([compliance_dropdown], className=""),
],
className="grid gap-x-4 gap-y-4 sm:grid-cols-2 lg:grid-cols-4 lg:gap-y-0",
),
html.Div(
[
html.Div(
className="flex flex-col col-span-12 md:col-span-4 gap-y-4",
id="overall_status_result_graph",
),
html.Div(
className="flex flex-col col-span-12 md:col-span-7 md:col-end-13 gap-y-4",
id="security_level_graph",
),
html.Div(
className="flex flex-col col-span-12 md:col-span-2 gap-y-4",
id="",
),
],
className="grid gap-x-4 gap-y-4 grid-cols-12 lg:gap-y-0",
),
html.H4(
"Details compliance:",
className="text-prowler-stone-900 text-lg font-bold",
),
html.Div(className="flex flex-wrap", id="output"),
],
className="grid gap-x-8 gap-y-8 2xl:container mx-auto",
)

View File

@@ -0,0 +1,600 @@
# Standard library imports
import csv
import glob
import importlib
import os
import re
import warnings
# Third-party imports
import dash
import pandas as pd
import plotly.express as px
from dash import callback, dcc, html
from dash.dependencies import Input, Output
# Config import
from dashboard.config import (
encoding_format,
fail_color,
folder_path_compliance,
info_color,
manual_color,
pass_color,
)
from dashboard.lib.dropdowns import (
create_account_dropdown_compliance,
create_compliance_dropdown,
create_date_dropdown_compliance,
create_region_dropdown_compliance,
)
from dashboard.lib.layouts import create_layout_compliance
# Suppress warnings
warnings.filterwarnings("ignore")
# Global variables
# TODO: Create a flag to let the user put a custom path
csv_files = []
for file in glob.glob(os.path.join(folder_path_compliance, "*.csv")):
with open(file, "r", newline="", encoding=encoding_format) as csvfile:
reader = csv.reader(csvfile)
num_rows = sum(1 for row in reader)
if num_rows > 1:
csv_files.append(file)
def load_csv_files(csv_files):
# Load CSV files into a single pandas DataFrame.
dfs = []
results = []
for file in csv_files:
df = pd.read_csv(file, sep=";", on_bad_lines="skip")
if "CHECKID" in df.columns:
dfs.append(df)
result = file
result = result.split("/")[-1]
result = re.sub(r"^.*?_", "", result)
result = result.replace(".csv", "")
result = result.upper()
if "AWS" in result:
if "AWS_" in result:
result = result.replace("_AWS", "")
else:
result = result.replace("_AWS", " - AWS")
if "GCP" in result:
result = result.replace("_GCP", " - GCP")
if "AZURE" in result:
result = result.replace("_AZURE", " - AZURE")
if "KUBERNETES" in result:
result = result.replace("_KUBERNETES", " - KUBERNETES")
result = result[result.find("CIS_") :]
results.append(result)
unique_results = set(results)
results = list(unique_results)
# Check if there is any CIS report in the list and divide it in level 1 and level 2
new_results = []
old_results = results.copy()
for compliance_name in results:
if "CIS_" in compliance_name:
old_results.remove(compliance_name)
new_results.append(compliance_name + " - Level_1")
new_results.append(compliance_name + " - Level_2")
results = old_results + new_results
results.sort()
# Handle the case where there are no CSV files
try:
data = pd.concat(dfs, ignore_index=True)
except ValueError:
data = None
return data, results
data, results = load_csv_files(csv_files)
if data is None:
dash.register_page(__name__)
layout = html.Div(
[
html.Div(
[
html.H5(
"No data found, check if the CSV files are in the correct folder.",
className="card-title",
style={"text-align": "left"},
)
],
style={
"width": "99%",
"margin-right": "0.8%",
"margin-bottom": "10px",
},
)
]
)
else:
data["ASSESSMENTDATE"] = pd.to_datetime(data["ASSESSMENTDATE"])
data["ASSESSMENT_TIME"] = data["ASSESSMENTDATE"].dt.strftime("%Y-%m-%d %H:%M:%S")
data_values = data["ASSESSMENT_TIME"].unique()
data_values.sort()
data_values = data_values[::-1]
aux = []
for value in data_values:
if value.split(" ")[0] not in [aux[i].split(" ")[0] for i in range(len(aux))]:
aux.append(value)
data_values = aux
data = data[data["ASSESSMENT_TIME"].isin(data_values)]
data["ASSESSMENT_TIME"] = data["ASSESSMENT_TIME"].apply(lambda x: x.split(" ")[0])
# Select Compliance - Dropdown
compliance_dropdown = create_compliance_dropdown(results)
# Select Account - Dropdown
select_account_dropdown_list = ["All"]
# Append to the list the unique values of the columns ACCOUNTID, PROJECTID and SUBSCRIPTIONID if they exist
if "ACCOUNTID" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["ACCOUNTID"].unique()
)
if "PROJECTID" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["PROJECTID"].unique()
)
if "SUBSCRIPTIONID" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["SUBSCRIPTIONID"].unique()
)
if "SUBSCRIPTION" in data.columns:
select_account_dropdown_list = select_account_dropdown_list + list(
data["SUBSCRIPTION"].unique()
)
list_items = []
for item in select_account_dropdown_list:
if item.__class__.__name__ == "str" and "nan" not in item:
list_items.append(item)
account_dropdown = create_account_dropdown_compliance(list_items)
# Select Region - Dropdown
select_region_dropdown_list = ["All"]
# Append to the list the unique values of the column REGION or LOCATION if it exists
if "REGION" in data.columns:
# Handle the case where the column REGION is empty
data["REGION"] = data["REGION"].fillna("-")
select_region_dropdown_list = select_region_dropdown_list + list(
data["REGION"].unique()
)
if "LOCATION" in data.columns:
# Handle the case where the column LOCATION is empty
data["LOCATION"] = data["LOCATION"].fillna("-")
select_region_dropdown_list = select_region_dropdown_list + list(
data["LOCATION"].unique()
)
# Clear the list from None and NaN values
list_items = []
for item in select_region_dropdown_list:
if item.__class__.__name__ == "str":
list_items.append(item)
region_dropdown = create_region_dropdown_compliance(list_items)
# Select Date - Dropdown
date_dropdown = create_date_dropdown_compliance(
list(data["ASSESSMENT_TIME"].unique())
)
dash.register_page(__name__)
layout = create_layout_compliance(
account_dropdown, date_dropdown, region_dropdown, compliance_dropdown
)
@callback(
[
Output("output", "children"),
Output("overall_status_result_graph", "children"),
Output("security_level_graph", "children"),
Output("cloud-account-filter-compliance", "value"),
Output("cloud-account-filter-compliance", "options"),
Output("region-filter-compliance", "value"),
Output("region-filter-compliance", "options"),
Output("date-filter-analytics", "value"),
Output("date-filter-analytics", "options"),
],
Input("report-compliance-filter", "value"),
Input("cloud-account-filter-compliance", "value"),
Input("region-filter-compliance", "value"),
Input("date-filter-analytics", "value"),
)
def display_data(
analytics_input, account_filter, region_filter_analytics, date_filter_analytics
):
current_compliance = analytics_input
analytics_input = analytics_input.replace(" - ", "_")
analytics_input = analytics_input.lower()
# Check if the compliance selected is the level 1 or level 2 of the CIS
is_level_1 = "level_1" in analytics_input
analytics_input = analytics_input.replace("_level_1", "").replace("_level_2", "")
# Filter the data based on the compliance selected
files = [file for file in csv_files if analytics_input in file]
def load_csv_files(files):
"""Load CSV files into a single pandas DataFrame."""
dfs = []
for file in files:
df = pd.read_csv(file, sep=";", on_bad_lines="skip")
dfs.append(df.astype(str))
return pd.concat(dfs, ignore_index=True)
data = load_csv_files(files)
# Rename the column LOCATION to REGION for GCP or Azure
if "gcp" in analytics_input or "azure" in analytics_input:
data = data.rename(columns={"LOCATION": "REGION"})
# Add the column ACCOUNTID to the data if the provider is kubernetes
if "kubernetes" in analytics_input:
data.rename(columns={"CONTEXT": "ACCOUNTID"}, inplace=True)
data.rename(columns={"NAMESPACE": "REGION"}, inplace=True)
if "REQUIREMENTS_ATTRIBUTES_PROFILE" in data.columns:
data["REQUIREMENTS_ATTRIBUTES_PROFILE"] = data[
"REQUIREMENTS_ATTRIBUTES_PROFILE"
].apply(lambda x: x.split(" - ")[0])
# Filter the chosen level of the CIS
if is_level_1:
data = data[data["REQUIREMENTS_ATTRIBUTES_PROFILE"] == "Level 1"]
# Rename the column PROJECTID to ACCOUNTID for GCP
if data.columns.str.contains("PROJECTID").any():
data.rename(columns={"PROJECTID": "ACCOUNTID"}, inplace=True)
# Rename the column SUBSCRIPTIONID to ACCOUNTID for Azure
if data.columns.str.contains("SUBSCRIPTIONID").any():
data.rename(columns={"SUBSCRIPTIONID": "ACCOUNTID"}, inplace=True)
data["REGION"] = "-"
# Handle v3 azure cis compliance
if data.columns.str.contains("SUBSCRIPTION").any():
data.rename(columns={"SUBSCRIPTION": "ACCOUNTID"}, inplace=True)
data["REGION"] = "-"
# Filter ACCOUNT
if account_filter == ["All"]:
updated_cloud_account_values = data["ACCOUNTID"].unique()
elif "All" in account_filter and len(account_filter) > 1:
# Remove 'All' from the list
account_filter.remove("All")
updated_cloud_account_values = account_filter
elif len(account_filter) == 0:
updated_cloud_account_values = data["ACCOUNTID"].unique()
account_filter = ["All"]
else:
updated_cloud_account_values = account_filter
data = data[data["ACCOUNTID"].isin(updated_cloud_account_values)]
account_filter_options = list(data["ACCOUNTID"].unique())
account_filter_options = account_filter_options + ["All"]
for item in account_filter_options:
if "nan" in item or item.__class__.__name__ != "str" or item is None:
account_filter_options.remove(item)
# Filter REGION
if region_filter_analytics == ["All"]:
updated_region_account_values = data["REGION"].unique()
elif "All" in region_filter_analytics and len(region_filter_analytics) > 1:
# Remove 'All' from the list
region_filter_analytics.remove("All")
updated_region_account_values = region_filter_analytics
elif len(region_filter_analytics) == 0:
updated_region_account_values = data["REGION"].unique()
region_filter_analytics = ["All"]
else:
updated_region_account_values = region_filter_analytics
data = data[data["REGION"].isin(updated_region_account_values)]
region_filter_options = list(data["REGION"].unique())
region_filter_options = region_filter_options + ["All"]
for item in region_filter_options:
if item == "nan" or item.__class__.__name__ != "str":
region_filter_options.remove(item)
data["ASSESSMENTDATE"] = pd.to_datetime(data["ASSESSMENTDATE"], errors="coerce")
data["ASSESSMENTDATE"] = data["ASSESSMENTDATE"].dt.strftime("%Y-%m-%d %H:%M:%S")
# Choosing the date that is the most recent
data_values = data["ASSESSMENTDATE"].unique()
data_values.sort()
data_values = data_values[::-1]
aux = []
data_values = [str(i) for i in data_values]
for value in data_values:
if value.split(" ")[0] not in [aux[i].split(" ")[0] for i in range(len(aux))]:
aux.append(value)
data_values = [str(i) for i in aux]
data = data[data["ASSESSMENTDATE"].isin(data_values)]
data["ASSESSMENTDATE"] = data["ASSESSMENTDATE"].apply(lambda x: x.split(" ")[0])
options_date = data["ASSESSMENTDATE"].unique()
options_date.sort()
options_date = options_date[::-1]
# Filter DATE
if date_filter_analytics in options_date:
data = data[data["ASSESSMENTDATE"] == date_filter_analytics]
else:
date_filter_analytics = options_date[0]
data = data[data["ASSESSMENTDATE"] == date_filter_analytics]
if data.empty:
fig = px.pie()
pie_1 = dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "250px", "width": "250px", "right": "0px"},
)
return [
html.Div(
[
html.H5(
"No data found for this compliance",
className="card-title",
style={"text-align": "left"},
)
],
style={
"width": "99%",
"margin-right": "0.8%",
"margin-bottom": "10px",
},
)
]
else:
# Check cases where the compliance start with AWS_
if "aws_" in analytics_input:
analytics_input = analytics_input + "_aws"
try:
current = analytics_input.replace(".", "_")
compliance_module = importlib.import_module(
f"dashboard.compliance.{current}"
)
data.drop_duplicates(keep="first", inplace=True)
table = compliance_module.get_table(data)
except ModuleNotFoundError:
table = html.Div(
[
html.H5(
"No data found for this compliance",
className="card-title",
style={"text-align": "left", "color": "black"},
)
],
style={
"width": "99%",
"margin-right": "0.8%",
"margin-bottom": "10px",
},
)
df = data.copy()
df = df.groupby(["STATUS"]).size().reset_index(name="counts")
df = df.sort_values(by=["counts"], ascending=False)
# Pie 1
pie_1 = get_pie(df)
# Get the pie2 depending on the compliance
df = data.copy()
current_filter = ""
if "pci" in analytics_input:
pie_2 = get_bar_graph(df, "REQUIREMENTS_ID")
current_filter = "req_id"
elif (
"REQUIREMENTS_ATTRIBUTES_SECTION" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_SECTION"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_SECTION")
current_filter = "sections"
elif (
"REQUIREMENTS_ATTRIBUTES_CATEGORIA" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_CATEGORIA"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_CATEGORIA")
current_filter = "categorias"
elif (
"REQUIREMENTS_ATTRIBUTES_CATEGORY" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_CATEGORY"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_CATEGORY")
current_filter = "categories"
elif (
"REQUIREMENTS_ATTRIBUTES_SERVICE" in df.columns
and not df["REQUIREMENTS_ATTRIBUTES_SERVICE"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ATTRIBUTES_SERVICE")
current_filter = "services"
elif (
"REQUIREMENTS_ID" in df.columns
and not df["REQUIREMENTS_ID"].isnull().values.any()
):
pie_2 = get_bar_graph(df, "REQUIREMENTS_ID")
current_filter = "techniques"
else:
fig = px.pie()
fig.update_layout(
margin=dict(l=0, r=0, t=0, b=0),
autosize=True,
showlegend=False,
paper_bgcolor="#303030",
)
pie_2 = dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "250px", "width": "250px", "right": "0px"},
)
current_filter = "none"
# Analytics table
if not analytics_input:
analytics_input = ""
table_output = get_table(current_compliance, table)
overall_status_result_graph = get_graph(pie_1, "Overall Status Result")
security_level_graph = get_graph(
pie_2, f"Top 5 failed {current_filter} by findings"
)
return (
table_output,
overall_status_result_graph,
security_level_graph,
account_filter,
account_filter_options,
region_filter_analytics,
region_filter_options,
date_filter_analytics,
options_date,
)
def get_graph(pie, title):
return [
html.Span(
title,
className="text-center text-prowler-stone-900 uppercase text-xs font-bold",
),
html.Div(
[pie],
className="",
style={
"display": "flex",
"justify-content": "center",
"align-items": "center",
"margin-top": "7%",
},
),
]
def get_bar_graph(df, column_name):
df = df[df["STATUS"] == "FAIL"]
df = df.groupby([column_name, "STATUS"]).size().reset_index(name="counts")
df = df.sort_values(by=["counts"], ascending=True)
# take the top 5
df = df.tail(5)
colums = df[column_name].unique()
# Cut the text if it is too long
for i in range(len(colums)):
if len(colums[i]) > 15:
colums[i] = colums[i][:15] + "..."
fig = px.bar(
df,
x="counts",
y=colums,
color="STATUS",
color_discrete_map={"FAIL": fail_color},
orientation="h",
)
fig.update_layout(
margin=dict(l=0, r=0, t=0, b=0),
autosize=True,
showlegend=False,
xaxis_title=None,
yaxis_title=None,
font=dict(size=14, color="#292524"),
hoverlabel=dict(font_size=12),
paper_bgcolor="#FFF",
)
return dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "20rem", "width": "40rem"},
)
def get_pie(df):
# Define custom colors
color_mapping = {
"FAIL": fail_color,
"PASS": pass_color,
"INFO": info_color,
"WARN": "#260000",
"MANUAL": manual_color,
}
# Use the color_discrete_map parameter to map categories to custom colors
fig = px.pie(
df,
names="STATUS",
values="counts",
hole=0.7,
color="STATUS",
color_discrete_map=color_mapping,
)
fig.update_traces(
hovertemplate=None,
textposition="outside",
textinfo="percent+label",
rotation=50,
)
fig.update_layout(
margin=dict(l=0, r=0, t=0, b=0),
autosize=True,
showlegend=False,
font=dict(size=14, color="#292524"),
hoverlabel=dict(font_size=12),
paper_bgcolor="#FFF",
)
pie = dcc.Graph(
figure=fig,
config={"displayModeBar": False},
style={"height": "20rem", "width": "20rem"},
)
return pie
def get_table(current_compliance, table):
return [
html.Div(
[
html.H5(
f"{current_compliance}",
className="text-prowler-stone-900 text-md font-bold uppercase mb-4",
),
table,
],
className="relative flex flex-col bg-white shadow-provider rounded-xl px-4 py-3 flex-wrap w-full",
),
]

1222
dashboard/pages/overview.py Normal file

File diff suppressed because it is too large Load Diff

112
dashboard/src/input.css Normal file
View File

@@ -0,0 +1,112 @@
/*
/*
/*
/*
/* Use this file to add custom styles using Tailwind's utility classes. */
@tailwind base;
@tailwind components;
@tailwind utilities;
#_dash-app-content {
@apply bg-prowler-stone-500;
}
@layer components {
.custom-grid {
grid-template-columns: minmax(0, 16fr) repeat(11, minmax(0, 11fr));
}
.custom-grid-large {
grid-template-columns: minmax(0, 10fr) repeat(11, minmax(0, 11fr));
}
/* Styles for the accordion in the compliance page */
#_dash-app-content .accordion .accordion-header .accordion-button {
@apply text-prowler-stone-900 inline-block px-4 text-xs font-bold uppercase transition-all rounded-lg bg-prowler-stone-300 hover:bg-prowler-stone-900/10;
}
#_dash-app-content .accordion .accordion-item {
@apply text-prowler-stone-900 bg-prowler-white rounded-lg;
}
#_dash-app-content .accordion .accordion-button:not(.collapsed) {
@apply text-prowler-stone-900 bg-prowler-stone-500;
}
#_dash-app-content .accordion .dash-table-container {
@apply grid;
}
#_dash-app-content .accordion table {
@apply rounded-lg;
}
/* Styles for thead */
#_dash-app-content .accordion th {
@apply text-prowler-white text-left bg-prowler-stone-900 text-xs py-1 font-bold;
}
/* Styles for td */
#_dash-app-content .accordion td {
@apply text-prowler-stone-900 text-left bg-prowler-white text-xs py-1 font-light;
}
/* Styles for table cells */
#_dash-app-content .accordion table tbody thead,
#_dash-app-content .accordion table tbody tr {
@apply w-full;
}
/* Check ID */
#_dash-app-content .accordion table th:nth-child(1) {
@apply w-[60%];
}
/* Status */
#_dash-app-content .accordion table th:nth-child(2) {
@apply w-[10%] text-center;
}
#_dash-app-content .accordion table td:nth-child(2) {
@apply text-center;
}
/* Region */
#_dash-app-content .accordion table th:nth-child(3) {
@apply w-[10%];
}
/* Account ID */
#_dash-app-content .accordion table th:nth-child(4) {
@apply w-[10%];
}
/* Resource ID */
#_dash-app-content .accordion table th:nth-child(5) {
@apply w-[10%];
}
#_dash-app-content .compliance-data-layout,
#_dash-app-content .accordion-body,
#_dash-app-content .compliance-data-layout .accordion.accordion-flush {
@apply grid gap-y-4;
}
#_dash-app-content .accordion-inner--child,
#_dash-app-content .accordion-inner {
@apply relative;
}
#_dash-app-content .info-bar {
@apply absolute left-1/2 transform -translate-x-1/2 top-2 h-8 z-50;
}
#_dash-app-content .info-bar-child {
@apply absolute right-6 top-2 w-auto h-8 z-50;
}
@layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}

View File

@@ -0,0 +1,90 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./assets/**/*.{py,html,js}",
"./components/**/*.{py,html,js}",
"./pages/**/*.{py,html,js}",
"./utils/**/*.{py,html,js}",
"./app.py",
],
theme: {
extend: {
colors: {
prowler: {
stone: {
950: "#1C1917",
900: "#292524",
500: "#E7E5E4",
300: "#F5F5F4",
},
gray: {
900: "#9bAACF",
700: "#BEC8E4",
500: "#C8D0E7",
300: "#E4EBF5",
},
status: {
passed: "#1FB53F",
failed: "#A3231F",
},
lime: "#84CC16",
white: "#FFFFFF",
error: "#B91C1C",
},
},
fontSize: {
'3xs': '0.625rem', // 10px
'2xs': '0.6875rem', // 11px
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '1.25rem', // 20px
'2xl': '1.375rem', // 22px
'2xxl': '1.5rem', // 24px
'3xl': '1.75rem', // 28px
'4xl': '2rem', // 32px
'5xl': '2.25rem', // 36px
'6xl': '2.75rem', // 44px
'7xl': '3.5rem' // 56px
},
fontWeight: {
light: 300,
regular: 400,
medium: 500,
bold: 700,
heavy: 800
},
lineHeight: {
14: "0.875rem", // 14px
22: "1.375rem", // 22px
26: "1.625rem", // 26px
28: "1.75rem", // 28px
30: "1.875rem", // 30px
32: "2rem", // 32px
34: "2.125rem", // 34px
36: "2.25rem", // 36px
40: "2.5rem", // 40px
44: "2.75rem", // 44px
48: "3rem", // 48px
56: "3.5rem", // 56px
68: "4.25rem", // 68px
},
boxShadow: {
"provider":
".3rem .3rem .6rem #c8d0e7, -.2rem -.2rem .5rem #FFF",
"box-up":
"0.3rem 0.3rem 0.6rem #c8d0e7, -0.2rem -0.2rem 0.5rem #FFF",
"box-down":
"inset .2rem .2rem .5rem #c8d0e7, inset -.2rem -.2rem .5rem #FFF",
},
backgroundImage: {
"gradient-passed":
"linear-gradient(127.43deg, #F1F5F8 -177.68%, #4ADE80 87.35%)",
"gradient-failed":
"linear-gradient(127.43deg, #F1F5F8 -177.68%, #EF4444 87.35%)",
},
},
},
plugins: [],
};

View File

@@ -1,9 +0,0 @@
# Audit Info
In each Prowler provider we have a Python object called `audit_info` which is in charge of keeping the credentials, the configuration and the state of each audit, and it's passed to each service during the `__init__`.
- AWS: https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/lib/audit_info/models.py#L34-L54
- GCP: https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/lib/audit_info/models.py#L7-L30
- Azure: https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/lib/audit_info/models.py#L17-L31
This `audit_info` object is shared during the Prowler execution and for that reason is important to mock it in each test to isolate them. See the [testing guide](./unit-testing.md) for more information.

View File

@@ -5,9 +5,15 @@ Here you can find how to create new checks for Prowler.
**To create a check is required to have a Prowler provider service already created, so if the service is not present or the attribute you want to audit is not retrieved by the service, please refer to the [Service](./services.md) documentation.**
## Introduction
The checks are the fundamental piece of Prowler. A check is a simply piece of code that ensures if something is configured against cybersecurity best practices. Then the check generates a finding with the result and includes the check's metadata to give the user more contextual information about the result, the risk and how to remediate it.
To create a new check for a supported Prowler provider, you will need to create a folder with the check name inside the specific service for the selected provider.
We are going to use the `ec2_ami_public` check form the `AWS` provider as an example. So the folder name will `prowler/providers/aws/services/ec2/ec2_ami_public` (following the format `prowler/providers/<provider>/services/<service>/<check_name>`), with the name of check following the pattern: `service_subservice/resource_action`.
We are going to use the `ec2_ami_public` check from the `AWS` provider as an example. So the folder name will be `prowler/providers/aws/services/ec2/ec2_ami_public` (following the format `prowler/providers/<provider>/services/<service>/<check_name>`), with the name of check following the pattern: `service_subservice_resource_action`.
???+ note
A subservice is an specific component of a service that is gonna be audited. Sometimes it could be the shortened name of the class attribute that is gonna be accessed in the check.
Inside that folder, we need to create three files:
@@ -101,12 +107,19 @@ All the checks MUST fill the `report.status` and `report.status_extended` with t
- Status -- `report.status`
- `PASS` --> If the check is passing against the configured value.
- `FAIL` --> If the check is passing against the configured value.
- `INFO` --> This value cannot be used unless a manual operation is required in order to determine if the `report.status` is whether `PASS` or `FAIL`.
- `FAIL` --> If the check is failing against the configured value.
- `MANUAL` --> This value cannot be used unless a manual operation is required in order to determine if the `report.status` is whether `PASS` or `FAIL`.
- Status Extended -- `report.status_extended`
- MUST end in a dot `.`
- MUST include the service audited with the resource and a brief explanation of the result generated, e.g.: `EC2 AMI ami-0123456789 is not public.`
### Check Region
All the checks MUST fill the `report.region` with the following criteria:
- If the audited resource is regional use the `region` (the name changes depending on the provider: `location` in Azure and GCP and `namespace` in K8s) attribute within the resource object.
- If the audited resource is global use the `service_client.region` within the service client object.
### Resource ID, Name and ARN
All the checks MUST fill the `report.resource_id` and `report.resource_arn` with the following criteria:
@@ -118,7 +131,7 @@ All the checks MUST fill the `report.resource_id` and `report.resource_arn` with
- Resource ARN -- `report.resource_arn`
- AWS Account --> Root ARN `arn:aws:iam::123456789012:root`
- AWS Resource --> Resource ARN
- Root resource --> Root ARN `arn:aws:iam::123456789012:root`
- Root resource --> Resource Type ARN `f"arn:{service_client.audited_partition}:<service_name>:{service_client.region}:{service_client.audited_account}:<resource_type>"`
- GCP
- Resource ID -- `report.resource_id`
- GCP Resource --> Resource ID
@@ -133,7 +146,7 @@ All the checks MUST fill the `report.resource_id` and `report.resource_arn` with
### Python Model
The following is the Python model for the check's class.
As per August 5th 2023 the `Check_Metadata_Model` can be found [here](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py#L59-L80).
As per April 11th 2024 the `Check_Metadata_Model` can be found [here](https://github.com/prowler-cloud/prowler/blob/master/prowler/lib/check/models.py#L36-L82).
```python
class Check(ABC, Check_Metadata_Model):
@@ -189,14 +202,17 @@ aws:
As you can see in the above code, within the service client, in this case the `ec2_client`, there is an object called `audit_config` which is a Python dictionary containing the values read from the configuration file.
In order to use it, you have to check first if the value is present in the configuration file. If the value is not present, you can create it in the `config.yaml` file and then, read it from the check.
> It is mandatory to always use the `dictionary.get(value, default)` syntax to set a default value in the case the configuration value is not present.
???+ note
It is mandatory to always use the `dictionary.get(value, default)` syntax to set a default value in the case the configuration value is not present.
## Check Metadata
Each Prowler check has metadata associated which is stored at the same level of the check's folder in a file called A `check_name.metadata.json` containing the check's metadata.
> We are going to include comments in this example metadata JSON but they cannot be included because the JSON format does not allow comments.
???+ note
We are going to include comments in this example metadata JSON but they cannot be included because the JSON format does not allow comments.
```json
{
@@ -233,11 +249,11 @@ Each Prowler check has metadata associated which is stored at the same level of
# Code holds different methods to remediate the FAIL finding
"Code": {
# CLI holds the command in the provider native CLI to remediate it
"CLI": "https://docs.bridgecrew.io/docs/public_8#cli-command",
"CLI": "https://docs.prowler.com/checks/public_8#cli-command",
# NativeIaC holds the native IaC code to remediate it, use "https://docs.bridgecrew.io/docs"
"NativeIaC": "",
# Other holds the other commands, scripts or code to remediate it, use "https://www.trendmicro.com/cloudoneconformity"
"Other": "https://docs.bridgecrew.io/docs/public_8#aws-console",
"Other": "https://docs.prowler.com/checks/public_8#aws-console",
# Terraform holds the Terraform code to remediate it, use "https://docs.bridgecrew.io/docs"
"Terraform": ""
},

View File

@@ -0,0 +1,45 @@
# Debugging
Debugging in Prowler make things easier!
If you are developing Prowler, it's possible that you will encounter some situations where you have to inspect the code in depth to fix some unexpected issues during the execution. To do that, if you are using VSCode you can run the code using the integrated debugger. Please, refer to this [documentation](https://code.visualstudio.com/docs/editor/debugging) for guidance about the debugger in VSCode.
The following file is an example of the [debugging configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) file that you can add to [Virtual Studio Code](https://code.visualstudio.com/).
This file should inside the *.vscode* folder and its name has to be *launch.json*:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "prowler.py",
"args": [
"aws",
"-f",
"eu-west-1",
"--service",
"cloudwatch",
"--log-level",
"ERROR",
"-p",
"dev",
],
"console": "integratedTerminal",
"justMyCode": false
},
{
"name": "Python: Debug Tests",
"type": "python",
"request": "launch",
"program": "${file}",
"purpose": [
"debug-test"
],
"console": "integratedTerminal",
"justMyCode": false
}
]
}
```

View File

@@ -1,6 +1,6 @@
## Contribute with documentation
We use `mkdocs` to build this Prowler documentation site so you can easily contribute back with new docs or improving them.
We use `mkdocs` to build this Prowler documentation site so you can easily contribute back with new docs or improving them. To install all necessary dependencies use `poetry install --with docs`.
1. Install `mkdocs` with your favorite package manager.
2. Inside the `prowler` repository folder run `mkdocs serve` and point your browser to `http://localhost:8000` and you will see live changes to your local copy of this documentation site.

View File

@@ -1,6 +1,6 @@
# Developer Guide
You can extend Prowler in many different ways, in most cases you will want to create your own checks and compliance security frameworks, here is where you can learn about how to get started with it. We also include how to create custom outputs, integrations and more.
You can extend Prowler Open Source in many different ways, in most cases you will want to create your own checks and compliance security frameworks, here is where you can learn about how to get started with it. We also include how to create custom outputs, integrations and more.
## Get the code and install all dependencies
@@ -16,7 +16,7 @@ pip install poetry
```
Then install all dependencies including the ones for developers:
```
poetry install
poetry install --with dev
poetry shell
```
@@ -31,7 +31,9 @@ You should get an output like the following:
pre-commit installed at .git/hooks/pre-commit
```
Before we merge any of your pull requests we pass checks to the code, we use the following tools and automation to make sure the code is secure and dependencies up-to-dated (these should have been already installed if you ran `pipenv install -d`):
Before we merge any of your pull requests we pass checks to the code, we use the following tools and automation to make sure the code is secure and dependencies up-to-dated:
???+ note
These should have been already installed if you ran `poetry install --with dev`
- [`bandit`](https://pypi.org/project/bandit/) for code security review.
- [`safety`](https://pypi.org/project/safety/) and [`dependabot`](https://github.com/features/security) for dependencies.

View File

@@ -0,0 +1,281 @@
# Create a new Provider for Prowler
Here you can find how to create a new Provider in Prowler to give support for making all security checks needed and make your cloud safer!
## Introduction
Providers are the foundation on which Prowler is built, a simple definition for a cloud provider could be "third-party company that offers a platform where any IT resource you need is available at any time upon request". The most well-known cloud providers are Amazon Web Services, Azure from Microsoft and Google Cloud which are already supported by Prowler.
To create a new provider that is not supported now by Prowler and add your security checks you must create a new folder to store all the related files within it (services, checks, etc.). It must be store in route `prowler/providers/<new_provider_name>/`.
Inside that folder, you MUST create the following files and folders:
- A `lib` folder: to store all extra functions.
- A `services` folder: to store all [services](./services.md) to audit.
- An empty `__init__.py`: to make Python treat this service folder as a package.
- A `<new_provider_name>_provider.py`, containing all the provider's logic necessary to get authenticated in the provider, configurations and extra data useful for final report.
- A `models.py`, containing all the models necessary for the new provider.
## Provider
The structure for Prowler's providers is set up in such a way that they can be utilized through a generic service specific to each provider. This is achieved by passing the required parameters to the constructor, which in turn initializes all the necessary session values.
### Base Class
All the providers in Prowler inherits from the same [base class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/common/provider.py). It is an [abstract base class](https://docs.python.org/3/library/abc.html) that defines the interface for all provider classes. The code of the class is the next:
```python title="Provider Base Class"
from abc import ABC, abstractmethod
from typing import Any
class Provider(ABC):
"""
The Provider class is an abstract base class that defines the interface for all provider classes in the auditing system.
Attributes:
type (property): The type of the provider.
identity (property): The identity of the provider for auditing.
session (property): The session of the provider for auditing.
audit_config (property): The audit configuration of the provider.
output_options (property): The output configuration of the provider for auditing.
Methods:
print_credentials(): Displays the provider's credentials used for auditing in the command-line interface.
setup_session(): Sets up the session for the provider.
get_output_mapping(): Returns the output mapping between the provider and the generic model.
validate_arguments(): Validates the arguments for the provider.
get_checks_to_execute_by_audit_resources(): Returns a set of checks based on the input resources to scan.
Note:
This is an abstract base class and should not be instantiated directly. Each provider should implement its own
version of the Provider class by inheriting from this base class and implementing the required methods and properties.
"""
@property
@abstractmethod
def type(self) -> str:
"""
type method stores the provider's type.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def identity(self) -> str:
"""
identity method stores the provider's identity to audit.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@abstractmethod
def setup_session(self) -> Any:
"""
setup_session sets up the session for the provider.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def session(self) -> str:
"""
session method stores the provider's session to audit.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def audit_config(self) -> str:
"""
audit_config method stores the provider's audit configuration.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@abstractmethod
def print_credentials(self) -> None:
"""
print_credentials is used to display in the CLI the provider's credentials used to audit.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@property
@abstractmethod
def output_options(self) -> str:
"""
output_options method returns the provider's audit output configuration.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@output_options.setter
@abstractmethod
def output_options(self, value: str) -> Any:
"""
output_options.setter sets the provider's audit output configuration.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@abstractmethod
def get_output_mapping(self) -> dict:
"""
get_output_mapping returns the output mapping between the provider and the generic model.
This method needs to be created in each provider.
"""
raise NotImplementedError()
def validate_arguments(self) -> None:
"""
validate_arguments validates the arguments for the provider.
This method can be overridden in each provider if needed.
"""
raise NotImplementedError()
def get_checks_to_execute_by_audit_resources(self) -> set:
"""
get_checks_to_execute_by_audit_resources returns a set of checks based on the input resources to scan.
This is a fallback that returns None if the service has not implemented this function.
"""
return set()
@property
@abstractmethod
def mutelist(self):
"""
mutelist method returns the provider's mutelist.
This method needs to be created in each provider.
"""
raise NotImplementedError()
@mutelist.setter
@abstractmethod
def mutelist(self, path: str):
"""
mutelist.setter sets the provider's mutelist.
This method needs to be created in each provider.
"""
raise NotImplementedError()
```
### Provider Class
Due to the complexity and differences of each provider use the rest of the providers as a template for the implementation.
- [AWS](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/aws_provider.py)
- [GCP](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/gcp_provider.py)
- [Azure](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/azure_provider.py)
- [Kubernetes](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/kubernetes_provider.py)
To facilitate understanding here is a pseudocode of how the most basic provider could be with examples.
```python title="Provider Example Class"
# Library imports to authenticate in the Provider
from prowler.config.config import load_and_validate_config_file
from prowler.lib.logger import logger
from prowler.lib.mutelist.mutelist import parse_mutelist_file
from prowler.lib.utils.utils import print_boxes
from prowler.providers.common.models import Audit_Metadata
from prowler.providers.common.provider import Provider
from prowler.providers.<new_provider_name>.models import (
# All providers models needed
ProvierSessionModel,
ProvierIdentityModel,
ProvierOutputOptionsModel
)
class NewProvider(Provider):
# All properties from the class, some of this are properties in the base class
_type: str = "<provider_name>"
_session: <ProvierSessionModel>
_identity: <ProvierIdentityModel>
_audit_config: dict
_output_options: ProvierOutputOptionsModel
_mutelist: dict
audit_metadata: Audit_Metadata
def __init__(self, arguments):
"""
Initializes the NewProvider instance.
Args:
arguments (dict): A dictionary containing configuration arguments.
"""
logger.info("Setting <NewProviderName> provider ...")
# First get from arguments the necesary from the cloud acount (subscriptions or projects or whatever the provider use for storing services)
# Set the session with the method enforced by parent class
self._session = self.setup_session(credentials_file)
# Set the Identity class normaly the provider class give by Python provider library
self._identity = <ProvierIdentityModel>()
# Set the provider configuration
self._audit_config = load_and_validate_config_file(
self._type, arguments.config_file
)
# All enforced properties by the parent class
@property
def identity(self):
return self._identity
@property
def session(self):
return self._session
@property
def type(self):
return self._type
@property
def audit_config(self):
return self._audit_config
@property
def output_options(self):
return self._output_options
def setup_session(self, <all_needed_for_auth>):
"""
Sets up the Provider session.
Args:
<all_needed_for_auth> Can include all necessary arguments to setup the session
Returns:
Credentials necesary to communicate with the provider.
"""
pass
"""
This method is enforced by parent class and is used to print all relevant
information during the prowler execution as a header of execution.
Normally the Account ID, User name or stuff like this is displayed in colors using the colorama module (Fore).
"""
def print_credentials(self):
pass
```

View File

@@ -23,7 +23,7 @@ Each file version of a framework will have the following structure at high level
"Requirements": [
{
"Id": "<unique-id>",
"Description": "Requiemente full description",
"Description": "Requirement full description",
"Checks": [
"Here is the prowler check or checks that is going to be executed"
],
@@ -38,4 +38,4 @@ Each file version of a framework will have the following structure at high level
}
```
Finally, to have a proper output file for your reports, your framework data model has to be created in `prowler/lib/outputs/models.py` and also the CLI table output in `prowler/lib/outputs/compliance.py`.
Finally, to have a proper output file for your reports, your framework data model has to be created in `prowler/lib/outputs/models.py` and also the CLI table output in `prowler/lib/outputs/compliance.py`. Also, you need to add a new conditional in `prowler/lib/outputs/file_descriptors.py` if you create a new CSV model.

View File

@@ -4,33 +4,36 @@ Here you can find how to create a new service, or to complement an existing one,
## Introduction
To create a new service, you will need to create a folder inside the specific provider, i.e. `prowler/providers/<provider>/services/<service>/`.
In Prowler, a service is basically a solution that is offered by a cloud provider i.e. [ec2](https://aws.amazon.com/ec2/). Essentially it is a class that stores all the necessary stuff that we will need later in the checks to audit some aspects of our Cloud account.
To create a new service, you will need to create a folder inside the specific provider, i.e. `prowler/providers/<provider>/services/<new_service_name>/`.
Inside that folder, you MUST create the following files:
- An empty `__init__.py`: to make Python treat this service folder as a package.
- A `<service>_service.py`, containing all the service's logic and API calls.
- A `<service>_client_.py`, containing the initialization of the service's class we have just created so the checks's checks can use it.
- A `<new_service_name>_service.py`, containing all the service's logic and API calls.
- A `<new_service_name>_client_.py`, containing the initialization of the service's class we have just created so the checks's checks can use it.
## Service
The Prowler's service structure is the following and the way to initialise it is just by importing the service client in a check.
## Service Base Class
### Service Base Class
All the Prowler provider's services inherits from a base class depending on the provider used.
- [AWS Service Base Class](https://github.com/prowler-cloud/prowler/blob/22f8855ad7dad2e976dabff78611b643e234beaf/prowler/providers/aws/lib/service/service.py)
- [GCP Service Base Class](https://github.com/prowler-cloud/prowler/blob/22f8855ad7dad2e976dabff78611b643e234beaf/prowler/providers/gcp/lib/service/service.py)
- [Azure Service Base Class](https://github.com/prowler-cloud/prowler/blob/22f8855ad7dad2e976dabff78611b643e234beaf/prowler/providers/azure/lib/service/service.py)
- [AWS Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/aws/lib/service/service.py)
- [GCP Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/azure/lib/service/service.py)
- [Azure Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/gcp/lib/service/service.py)
- [Kubernetes Service Base Class](https://github.com/prowler-cloud/prowler/blob/master/prowler/providers/kubernetes/lib/service/service.py)
Each class is used to initialize the credentials and the API's clients to be used in the service. If some threading is used it must be coded there.
## Service Class
### Service Class
Due to the complexity and differencies of each provider API we are going to use an example service to guide you in how can it be created.
Due to the complexity and differences of each provider API we are going to use an example service to guide you in how can it be created.
The following is the `<service>_service.py` file:
The following is the `<new_service_name>_service.py` file:
```python title="Service Class"
from datetime import datetime
@@ -55,12 +58,12 @@ from prowler.providers.<provider>.lib.service.service import ServiceParentClass
# Create a class for the Service
################## <Service>
class <Service>(ServiceParentClass):
def __init__(self, audit_info):
def __init__(self, provider):
# Call Service Parent Class __init__
# We use the __class__.__name__ to get it automatically
# from the Service Class name but you can pass a custom
# string if the provider's API service name is different
super().__init__(__class__.__name__, audit_info)
super().__init__(__class__.__name__, provider)
# Create an empty dictionary of items to be gathered,
# using the unique ID as the dictionary key
@@ -175,10 +178,12 @@ class <Service>(ServiceParentClass):
f"{<item>.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
```
???+note
To avoid fake findings, when Prowler can't retrieve the items, because an Access Denied or similar error, we set that items value as `None`.
### Service Models
#### Service Models
For each class object we need to model we use the Pydantic's [BaseModel](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel) to take advantage of the data validation.
Service models are classes that are used in the service to design all that we need to store in each class object extrated from API calls. We use the Pydantic's [BaseModel](https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel) to take advantage of the data validation.
```python title="Service Model"
# In each service class we have to create some classes using
@@ -202,7 +207,7 @@ class <Item>(BaseModel):
tags: Optional[list]
"""<Items>[].tags"""
```
### Service Objects
#### Service Objects
In the service each group of resources should be created as a Python [dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries). This is because we are performing lookups all the time and the Python dictionary lookup has [O(1) complexity](https://en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions).
We MUST set as the dictionary key a unique ID, like the resource Unique ID or ARN.
@@ -213,17 +218,17 @@ self.vpcs = {}
self.vpcs["vpc-01234567890abcdef"] = VPC_Object_Class()
```
## Service Client
### Service Client
Each Prowler service requires a service client to use the service in the checks.
The following is the `<service>_client.py` containing the initialization of the service's class we have just created so the service's checks can use them:
The following is the `<new_service_name>_client.py` containing the initialization of the service's class we have just created so the service's checks can use them:
```python
from prowler.providers.<provider>.lib.audit_info.audit_info import audit_info
from prowler.providers.<provider>.services.<service>.<service>_service import <Service>
from prowler.providers.common.common import get_global_provider
from prowler.providers.<provider>.services.<new_service_name>.<new_service_name>_service import <Service>
<service>_client = <Service>(audit_info)
<new_service_name>_client = <Service>(get_global_provider())
```
## Permissions

View File

@@ -40,13 +40,15 @@ Other commands to run tests:
- Run tests for a provider service: `pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>`
- Run tests for a provider check: `pytest -n auto -vvv -s -x tests/providers/<provider>/services/<service>/<check>`
> Refer to the [pytest documentation](https://docs.pytest.org/en/7.1.x/getting-started.html) documentation for more information.
???+ note
Refer to the [pytest documentation](https://docs.pytest.org/en/7.1.x/getting-started.html) documentation for more information.
## AWS
For the AWS provider we have ways to test a Prowler check based on the following criteria:
> Note: We use and contribute to the [Moto](https://github.com/getmoto/moto) library which allows us to easily mock out tests based on AWS infrastructure. **It's awesome!**
???+ note
We use and contribute to the [Moto](https://github.com/getmoto/moto) library which allows us to easily mock out tests based on AWS infrastructure. **It's awesome!**
- AWS API calls covered by [Moto](https://github.com/getmoto/moto):
- Service tests with `@mock_<service>`
@@ -60,50 +62,6 @@ For the AWS provider we have ways to test a Prowler check based on the following
In the following section we are going to explain all of the above scenarios with examples. The main difference between those scenarios comes from if the [Moto](https://github.com/getmoto/moto) library covers the AWS API calls made by the service. You can check the covered API calls [here](https://github.com/getmoto/moto/blob/master/IMPLEMENTATION_COVERAGE.md).
An important point for the AWS testing is that in each check we MUST have a unique `audit_info` which is the key object during the AWS execution to isolate the test execution.
Check the [Audit Info](./audit-info.md) section to get more details.
```python
# We need to import the AWS_Audit_Info and the Audit_Metadata
# to set the audit_info to call AWS APIs
from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info
from prowler.providers.common.models import Audit_Metadata
AWS_ACCOUNT_NUMBER = "123456789012"
def set_mocked_audit_info(self):
audit_info = AWS_Audit_Info(
session_config=None,
original_session=None,
audit_session=session.Session(
profile_name=None,
botocore_session=None,
),
audit_config=None,
audited_account=AWS_ACCOUNT_NUMBER,
audited_account_arn=f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:root",
audited_user_id=None,
audited_partition="aws",
audited_identity_arn=None,
profile=None,
profile_region=None,
credentials=None,
assumed_role_info=None,
audited_regions=["us-east-1", "eu-west-1"],
organizations_metadata=None,
audit_resources=None,
mfa_enabled=False,
audit_metadata=Audit_Metadata(
services_scanned=0,
expected_checks=[],
completed_checks=0,
audit_progress=0,
),
)
return audit_info
```
### Checks
For the AWS tests examples we are going to use the tests for the `iam_password_policy_uppercase` check.
@@ -146,29 +104,29 @@ class Test_iam_password_policy_uppercase:
# policy we want to set to False the RequireUppercaseCharacters
iam_client.update_account_password_policy(RequireUppercaseCharacters=False)
# We set a mocked audit_info for AWS not to share the same audit state
# between checks
current_audit_info = self.set_mocked_audit_info()
# The aws_provider is mocked using set_mocked_aws_provider to use it as the return of the get_global_provider method.
# this mocked provider is defined in fixtures
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
# The Prowler service import MUST be made within the decorated
# code not to make real API calls to the AWS service.
from prowler.providers.aws.services.iam.iam_service import IAM
# Prowler for AWS uses a shared object called `current_audit_info` where it stores
# the audit's state, credentials and configuration.
# Prowler for AWS uses a shared object called aws_provider where it stores
# the info related with the provider
with mock.patch(
"prowler.providers.aws.lib.audit_info.audit_info.current_audit_info",
new=current_audit_info,
"prowler.providers.common.common.get_global_provider",
return_value=aws_provider,
),
# We have to mock also the iam_client from the check to enforce that the iam_client used is the one
# created within this check because patch != import, and if you execute tests in parallel some objects
# can be already initialised hence the check won't be isolated
mock.patch(
"prowler.providers.aws.services.iam.iam_password_policy_uppercase.iam_password_policy_uppercase.iam_client",
new=IAM(current_audit_info),
new=IAM(aws_provider),
):
# We import the check within the two mocks not to initialise the iam_client with some shared information from
# the current_audit_info or the IAM service.
# the aws_provider or the IAM service.
from prowler.providers.aws.services.iam.iam_password_policy_uppercase.iam_password_policy_uppercase import (
iam_password_policy_uppercase,
)
@@ -195,7 +153,8 @@ class Test_iam_password_policy_uppercase:
If the IAM service for the check's we want to test is not covered by Moto, we have to inject the objects in the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock). As we have pointed above, we cannot instantiate the service since it will make real calls to the AWS APIs.
> The following example uses the IAM GetAccountPasswordPolicy which is covered by Moto but this is only for demonstration purposes.
???+ note
The following example uses the IAM GetAccountPasswordPolicy which is covered by Moto but this is only for demonstration purposes.
The following code shows how to use MagicMock to create the service objects.
@@ -232,9 +191,8 @@ class Test_iam_password_policy_uppercase:
expiration=True,
)
# We set a mocked audit_info for AWS not to share the same audit state
# between checks
current_audit_info = self.set_mocked_audit_info()
# We set a mocked aws_provider to unify providers, this way will isolate each test not to step on other tests configuration
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
# In this scenario we have to mock also the IAM service and the iam_client from the check to enforce # that the iam_client used is the one created within this check because patch != import, and if you # execute tests in parallel some objects can be already initialised hence the check won't be isolated.
# In this case we don't use the Moto decorator, we use the mocked IAM client for both objects
@@ -246,7 +204,7 @@ class Test_iam_password_policy_uppercase:
new=mocked_iam_client,
):
# We import the check within the two mocks not to initialise the iam_client with some shared information from
# the current_audit_info or the IAM service.
# the aws_provider or the IAM service.
from prowler.providers.aws.services.iam.iam_password_policy_uppercase.iam_password_policy_uppercase import (
iam_password_policy_uppercase,
)
@@ -325,23 +283,53 @@ class Test_iam_password_policy_uppercase:
Note that this does not use Moto, to keep it simple, but if you use any `moto`-decorators in addition to the patch, the call to `orig(self, operation_name, kwarg)` will be intercepted by Moto.
> The above code comes from here https://docs.getmoto.org/en/latest/docs/services/patching_other_services.html
???+ note
The above code comes from here https://docs.getmoto.org/en/latest/docs/services/patching_other_services.html
#### Mocking more than one service
Since we are mocking the provider, it can be customized setting multiple attributes to the provider:
```python
def set_mocked_aws_provider(
audited_regions: list[str] = [],
audited_account: str = AWS_ACCOUNT_NUMBER,
audited_account_arn: str = AWS_ACCOUNT_ARN,
audited_partition: str = AWS_COMMERCIAL_PARTITION,
expected_checks: list[str] = [],
profile_region: str = None,
audit_config: dict = {},
fixer_config: dict = {},
scan_unused_services: bool = True,
audit_session: session.Session = session.Session(
profile_name=None,
botocore_session=None,
),
original_session: session.Session = None,
enabled_regions: set = None,
arguments: Namespace = Namespace(),
create_default_organization: bool = True,
) -> AwsProvider:
```
If the test your are creating belongs to a check that uses more than one provider service, you should mock each of the services used. For example, the check `cloudtrail_logs_s3_bucket_access_logging_enabled` requires the CloudTrail and the S3 client, hence the service's mock part of the test will be as follows:
```python
with mock.patch(
"prowler.providers.aws.lib.audit_info.audit_info.current_audit_info",
new=mock_audit_info,
"prowler.providers.common.common.get_global_provider",
return_value=set_mocked_aws_provider(
[AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1]
),
), mock.patch(
"prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_client",
new=Cloudtrail(mock_audit_info),
new=Cloudtrail(
set_mocked_aws_provider([AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1])
),
), mock.patch(
"prowler.providers.aws.services.cloudtrail.cloudtrail_logs_s3_bucket_access_logging_enabled.cloudtrail_logs_s3_bucket_access_logging_enabled.s3_client",
new=S3(mock_audit_info),
new=S3(
set_mocked_aws_provider([AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1])
),
):
```
@@ -359,10 +347,10 @@ from prowler.providers.<provider>.services.<service>.<service>_client import <se
```
2. `<service>_client.py`:
```python
from prowler.providers.<provider>.lib.audit_info.audit_info import audit_info
from prowler.providers.common.common import get_global_provider
from prowler.providers.<provider>.services.<service>.<service>_service import <SERVICE>
<service>_client = <SERVICE>(audit_info)
<service>_client = <SERVICE>(mocked_provider)
```
Due to the above import path it's not the same to patch the following objects because if you run a bunch of tests, either in parallel or not, some clients can be already instantiated by another check, hence your test execution will be using another test's service instance:
@@ -380,19 +368,20 @@ A useful read about this topic can be found in the following article: https://st
Mocking a service client using the following code ...
Once the needed attributes are set for the mocked provider, you can use the mocked provider:
```python title="Mocking the service_client"
with mock.patch(
"prowler.providers.<provider>.lib.audit_info.audit_info.audit_info",
new=audit_info,
"prowler.providers.common.common.get_global_provider",
new=set_mocked_aws_provider([<region>]),
), mock.patch(
"prowler.providers.aws.services.<service>.<check>.<check>.<service>_client",
new=<SERVICE>(audit_info),
"prowler.providers.<provider>.services.<service>.<check>.<check>.<service>_client",
new=<SERVICE>(set_mocked_aws_provider([<region>])),
):
```
will cause that the service will be initialised twice:
1. When the `<SERVICE>(audit_info)` is mocked out using `mock.patch` to have the object ready for the patching.
2. At the `<service>_client.py` when we are patching it since the `mock.patch` needs to go to that object an initialise it, hence the `<SERVICE>(audit_info)` will be called again.
1. When the `<SERVICE>(set_mocked_aws_provider([<region>]))` is mocked out using `mock.patch` to have the object ready for the patching.
2. At the `<service>_client.py` when we are patching it since the `mock.patch` needs to go to that object an initialise it, hence the `<SERVICE>(set_mocked_aws_provider([<region>]))` will be called again.
Then, when we import the `<service>_client.py` at `<check>.py`, since we are mocking where the object is used, Python will use the mocked one.
@@ -404,24 +393,24 @@ Mocking a service client using the following code ...
```python title="Mocking the service and the service_client"
with mock.patch(
"prowler.providers.<provider>.lib.audit_info.audit_info.audit_info",
new=audit_info,
"prowler.providers.common.common.get_global_provider",
new=set_mocked_aws_provider([<region>]),
), mock.patch(
"prowler.providers.aws.services.<service>.<SERVICE>",
new=<SERVICE>(audit_info),
"prowler.providers.<provider>.services.<service>.<SERVICE>",
new=<SERVICE>(set_mocked_aws_provider([<region>])),
) as service_client, mock.patch(
"prowler.providers.aws.services.<service>.<service>_client.<service>_client",
"prowler.providers.<provider>.services.<service>.<service>_client.<service>_client",
new=service_client,
):
```
will cause that the service will be initialised once, just when the `<SERVICE>(audit_info)` is mocked out using `mock.patch`.
will cause that the service will be initialised once, just when the `set_mocked_aws_provider([<region>])` is mocked out using `mock.patch`.
Then, at the check_level when Python tries to import the client with `from prowler.providers.<provider>.services.<service>.<service>_client`, since it is already mocked out, the execution will continue using the `service_client` without getting into the `<service>_client.py`.
### Services
For testing the AWS services we have to follow the same logic as with the AWS checks, we have to check if the AWS API calls made by the service are covered by Moto and we have to test the service `__init__` to verifiy that the information is being correctly retrieved.
For testing the AWS services we have to follow the same logic as with the AWS checks, we have to check if the AWS API calls made by the service are covered by Moto and we have to test the service `__init__` to verify that the information is being correctly retrieved.
The service tests could act as *Integration Tests* since we test how the service retrieves the information from the provider, but since Moto or the custom mock objects mocks that calls this test will fall into *Unit Tests*.
@@ -433,79 +422,208 @@ Please refer to the [AWS checks tests](./unit-testing.md#checks) for more inform
For the GCP Provider we don't have any library to mock out the API calls we use. So in this scenario we inject the objects in the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).
The following code shows how to use MagicMock to create the service objects for a GCP check test.
The following code shows how to use MagicMock to create the service objects for a GCP check test. It is a real example adapted for informative purposes.
```python
# We need to import the unittest.mock to allow us to patch some objects
# not to use shared ones between test, hence to isolate the test
from re import search
from unittest import mock
# GCP Constants
GCP_PROJECT_ID = "123456789012"
# Import some constant values needed in every check
from tests.providers.gcp.gcp_fixtures import GCP_PROJECT_ID, set_mocked_gcp_provider
# We are going to create a test for the compute_firewall_rdp_access_from_the_internet_allowed check
class Test_compute_firewall_rdp_access_from_the_internet_allowed:
# We are going to create a test for the compute_project_os_login_enabled check
class Test_compute_project_os_login_enabled:
# We name the tests with test_<service>_<check_name>_<test_action>
def test_compute_compute_firewall_rdp_access_from_the_internet_allowed_one_compliant_rule_with_valid_port(self):
def test_one_compliant_project(self):
# Import the service resource model to create the mocked object
from prowler.providers.gcp.services.compute.compute_service import Project
# Create the custom Project object to be tested
project = Project(
id=GCP_PROJECT_ID,
enable_oslogin=True,
)
# Mocked client with MagicMock
compute_client = mock.MagicMock
# Assign GCP client configuration
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.region = "global"
compute_client.projects = [project]
# Import the service resource model to create the mocked object
from prowler.providers.gcp.services.compute.compute_service import Firewall
# Create the custom Firewall object to be tested
firewall = Firewall(
name="test",
id="1234567890",
source_ranges=["0.0.0.0/0"],
direction="INGRESS",
allowed_rules=[{"IPProtocol": "tcp", "ports": ["443"]}],
project_id=GCP_PROJECT_ID,
)
compute_client.firewalls = [firewall]
# In this scenario we have to mock also the Compute service and the compute_client from the check to enforce that the compute_client used is the one created within this check because patch != import, and if you execute tests in parallel some objects can be already initialised hence the check won't be isolated.
# In this case we don't use the Moto decorator, we use the mocked Compute client for both objects
# In this scenario we have to mock the app_client from the check to enforce that the compute_client used is the one created above
# And also is mocked the return value of get_global_provider function to return our GCP mocked provider defined in fixtures
with mock.patch(
"prowler.providers.gcp.services.compute.compute_service.Compute",
new=defender_client,
"prowler.providers.common.common.get_global_provider",
return_value=set_mocked_gcp_provider(),
), mock.patch(
"prowler.providers.gcp.services.compute.compute_client.compute_client",
new=defender_client,
"prowler.providers.gcp.services.compute.compute_project_os_login_enabled.compute_project_os_login_enabled.compute_client",
new=compute_client,
):
# We import the check within the two mocks not to initialise the iam_client with some shared information from
# the current_audit_info or the Compute service.
from prowler.providers.gcp.services.compute.compute_firewall_rdp_access_from_the_internet_allowed.compute_firewall_rdp_access_from_the_internet_allowed import (
compute_firewall_rdp_access_from_the_internet_allowed,
# We import the check within the two mocks
from prowler.providers.gcp.services.compute.compute_project_os_login_enabled.compute_project_os_login_enabled import (
compute_project_os_login_enabled,
)
# Once imported, we only need to instantiate the check's class
check = compute_firewall_rdp_access_from_the_internet_allowed()
check = compute_project_os_login_enabled()
# And then, call the execute() function to run the check
# against the IAM client we've set up.
# against the Compute client we've set up.
result = check.execute()
# Last but not least, we need to assert all the fields
# from the check's results
# Assert the expected results
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == f"Firewall {firewall.name} does not expose port 3389 (RDP) to the internet."
assert result[0].resource_name = firewall.name
assert result[0].resource_id == firewall.id
assert result[0].project_id = GCP_PROJECT_ID
assert result[0].location = compute_client.region
assert search(
f"Project {project.id} has OS Login enabled",
result[0].status_extended,
)
assert result[0].resource_id == project.id
assert result[0].location == "global"
assert result[0].project_id == GCP_PROJECT_ID
# Complementary test to make more coverage for different scenarios
def test_one_non_compliant_project(self):
from prowler.providers.gcp.services.compute.compute_service import Project
project = Project(
id=GCP_PROJECT_ID,
enable_oslogin=False,
)
compute_client = mock.MagicMock
compute_client.project_ids = [GCP_PROJECT_ID]
compute_client.projects = [project]
with mock.patch(
"prowler.providers.common.common.get_global_provider",
return_value=set_mocked_gcp_provider(),
), mock.patch(
"prowler.providers.gcp.services.compute.compute_project_os_login_enabled.compute_project_os_login_enabled.compute_client",
new=compute_client,
):
from prowler.providers.gcp.services.compute.compute_project_os_login_enabled.compute_project_os_login_enabled import (
compute_project_os_login_enabled,
)
check = compute_project_os_login_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert search(
f"Project {project.id} does not have OS Login enabled",
result[0].status_extended,
)
assert result[0].resource_id == project.id
assert result[0].location == "global"
assert result[0].project_id == GCP_PROJECT_ID
```
### Services
Coming soon ...
For testing Google Cloud Services, we have to follow the same logic as with the Google Cloud checks. We still mocking all API calls, but in this case, every API call to set up an attribute is defined in [fixtures file](https://github.com/prowler-cloud/prowler/blob/master/tests/providers/gcp/gcp_fixtures.py) in `mock_api_client` function. Remember that EVERY method of a service must be tested.
The following code shows a real example of a testing class, but it has more comments than usual for educational purposes.
```python title="BigQuery Service Test"
# We need to import the unittest.mock.patch to allow us to patch some objects
# not to use shared ones between test, hence to isolate the test
from unittest.mock import patch
# Import the class needed from the service file
from prowler.providers.gcp.services.bigquery.bigquery_service import BigQuery
# Necessary constans and functions from fixtures file
from tests.providers.gcp.gcp_fixtures import (
GCP_PROJECT_ID,
mock_api_client,
mock_is_api_active,
set_mocked_gcp_provider,
)
class TestBigQueryService:
# Only method needed to test full service
def test_service(self):
# In this case we are mocking the __is_api_active__ to ensure our mocked project is used
# And all the client to use our mocked API calls
with patch(
"prowler.providers.gcp.lib.service.service.GCPService.__is_api_active__",
new=mock_is_api_active,
), patch(
"prowler.providers.gcp.lib.service.service.GCPService.__generate_client__",
new=mock_api_client,
):
# Instantiate an object of class with the mocked provider
bigquery_client = BigQuery(
set_mocked_gcp_provider(project_ids=[GCP_PROJECT_ID])
)
# Check all attributes of the tested class is well set up according API calls mocked from GCP fixture file
assert bigquery_client.service == "bigquery"
assert bigquery_client.project_ids == [GCP_PROJECT_ID]
assert len(bigquery_client.datasets) == 2
assert bigquery_client.datasets[0].name == "unique_dataset1_name"
assert bigquery_client.datasets[0].id.__class__.__name__ == "str"
assert bigquery_client.datasets[0].region == "US"
assert bigquery_client.datasets[0].cmk_encryption
assert bigquery_client.datasets[0].public
assert bigquery_client.datasets[0].project_id == GCP_PROJECT_ID
assert bigquery_client.datasets[1].name == "unique_dataset2_name"
assert bigquery_client.datasets[1].id.__class__.__name__ == "str"
assert bigquery_client.datasets[1].region == "EU"
assert not bigquery_client.datasets[1].cmk_encryption
assert not bigquery_client.datasets[1].public
assert bigquery_client.datasets[1].project_id == GCP_PROJECT_ID
assert len(bigquery_client.tables) == 2
assert bigquery_client.tables[0].name == "unique_table1_name"
assert bigquery_client.tables[0].id.__class__.__name__ == "str"
assert bigquery_client.tables[0].region == "US"
assert bigquery_client.tables[0].cmk_encryption
assert bigquery_client.tables[0].project_id == GCP_PROJECT_ID
assert bigquery_client.tables[1].name == "unique_table2_name"
assert bigquery_client.tables[1].id.__class__.__name__ == "str"
assert bigquery_client.tables[1].region == "US"
assert not bigquery_client.tables[1].cmk_encryption
assert bigquery_client.tables[1].project_id == GCP_PROJECT_ID
```
As it can be confusing where all these values come from, I'll give an example to make this clearer. First we need to check
what is the API call used to obtain the datasets. In this case if we check the service the call is
`self.client.datasets().list(projectId=project_id)`.
Now in the fixture file we have to mock this call in our `MagicMock` client in the function `mock_api_client`. The best way to mock
is following the actual format, add one function where the client is passed to be changed, the format of this function name must be
`mock_api_<endpoint>_calls` (*endpoint* refers to the first attribute pointed after *client*).
In the example of BigQuery the function is called `mock_api_dataset_calls`. And inside of this function we found an assignation to
be used in the `__get_datasets__` method in BigQuery class:
```python
# Mocking datasets
dataset1_id = str(uuid4())
dataset2_id = str(uuid4())
client.datasets().list().execute.return_value = {
"datasets": [
{
"datasetReference": {
"datasetId": "unique_dataset1_name",
"projectId": GCP_PROJECT_ID,
},
"id": dataset1_id,
"location": "US",
},
{
"datasetReference": {
"datasetId": "unique_dataset2_name",
"projectId": GCP_PROJECT_ID,
},
"id": dataset2_id,
"location": "EU",
},
]
}
```
## Azure
@@ -513,80 +631,186 @@ Coming soon ...
For the Azure Provider we don't have any library to mock out the API calls we use. So in this scenario we inject the objects in the service client using [MagicMock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock).
The following code shows how to use MagicMock to create the service objects for a Azure check test.
The following code shows how to use MagicMock to create the service objects for a Azure check test. It is a real example adapted for informative purposes.
```python
```python title="app_ensure_http_is_redirected_to_https_test.py"
# We need to import the unittest.mock to allow us to patch some objects
# not to use shared ones between test, hence to isolate the test
from unittest import mock
from uuid import uuid4
# Azure Constants
AZURE_SUSCRIPTION = str(uuid4())
# Import some constans values needed in almost every check
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
# We are going to create a test for the Test_defender_ensure_defender_for_arm_is_on check
class Test_defender_ensure_defender_for_arm_is_on:
# We are going to create a test for the app_ensure_http_is_redirected_to_https check
class Test_app_ensure_http_is_redirected_to_https:
# We name the tests with test_<service>_<check_name>_<test_action>
def test_defender_defender_ensure_defender_for_arm_is_on_arm_pricing_tier_not_standard(self):
resource_id = str(uuid4())
def test_app_http_to_https_disabled(self):
resource_id = f"/subscriptions/{uuid4()}"
# Mocked client with MagicMock
defender_client = mock.MagicMock
app_client = mock.MagicMock
# Import the service resource model to create the mocked object
from prowler.providers.azure.services.defender.defender_service import Defender_Pricing
# Create the custom Defender object to be tested
defender_client.pricings = {
AZURE_SUSCRIPTION: {
"Arm": Defender_Pricing(
resource_id=resource_id,
pricing_tier="Not Standard",
free_trial_remaining_time=0,
)
}
}
# In this scenario we have to mock also the Defender service and the defender_client from the check to enforce that the defender_client used is the one created within this check because patch != import, and if you execute tests in parallel some objects can be already initialised hence the check won't be isolated.
# In this case we don't use the Moto decorator, we use the mocked Defender client for both objects
# In this scenario we have to mock the app_client from the check to enforce that the app_client used is the one created above
# And also is mocked the return value of get_global_provider function to return our Azure mocked provider defined in fixtures
with mock.patch(
"prowler.providers.azure.services.defender.defender_service.Defender",
new=defender_client,
"prowler.providers.common.common.get_global_provider",
return_value=set_mocked_azure_provider(),
), mock.patch(
"prowler.providers.azure.services.defender.defender_client.defender_client",
new=defender_client,
"prowler.providers.azure.services.app.app_ensure_http_is_redirected_to_https.app_ensure_http_is_redirected_to_https.app_client",
new=app_client,
):
# We import the check within the two mocks not to initialise the iam_client with some shared information from
# the current_audit_info or the Defender service.
from prowler.providers.azure.services.defender.defender_ensure_defender_for_arm_is_on.defender_ensure_defender_for_arm_is_on import (
defender_ensure_defender_for_arm_is_on,
# We import the check within the two mocks
from prowler.providers.azure.services.app.app_ensure_http_is_redirected_to_https.app_ensure_http_is_redirected_to_https import (
app_ensure_http_is_redirected_to_https,
)
# Import the service resource model to create the mocked object
from prowler.providers.azure.services.app.app_service import WebApp
# Create the custom App object to be tested
app_client.apps = {
AZURE_SUBSCRIPTION_ID: {
"app_id-1": WebApp(
resource_id=resource_id,
auth_enabled=True,
configurations=mock.MagicMock(),
client_cert_mode="Ignore",
https_only=False,
identity=None,
location="West Europe",
)
}
}
# Once imported, we only need to instantiate the check's class
check = defender_ensure_defender_for_arm_is_on()
check = app_ensure_http_is_redirected_to_https()
# And then, call the execute() function to run the check
# against the IAM client we've set up.
# against the App client we've set up.
result = check.execute()
# Last but not least, we need to assert all the fields
# from the check's results
# Assert the expected results
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Defender plan Defender for ARM from subscription {AZURE_SUSCRIPTION} is set to OFF (pricing tier not standard)"
== f"HTTP is not redirected to HTTPS for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_ID}'."
)
assert result[0].subscription == AZURE_SUSCRIPTION
assert result[0].resource_name == "Defender plan ARM"
assert result[0].resource_name == "app_id-1"
assert result[0].resource_id == resource_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
# Complementary test to make more coverage for different scenarios
def test_app_http_to_https_enabled(self):
resource_id = f"/subscriptions/{uuid4()}"
app_client = mock.MagicMock
with mock.patch(
"prowler.providers.common.common.get_global_provider",
return_value=set_mocked_azure_provider(),
), mock.patch(
"prowler.providers.azure.services.app.app_ensure_http_is_redirected_to_https.app_ensure_http_is_redirected_to_https.app_client",
new=app_client,
):
from prowler.providers.azure.services.app.app_ensure_http_is_redirected_to_https.app_ensure_http_is_redirected_to_https import (
app_ensure_http_is_redirected_to_https,
)
from prowler.providers.azure.services.app.app_service import WebApp
app_client.apps = {
AZURE_SUBSCRIPTION_ID: {
"app_id-1": WebApp(
resource_id=resource_id,
auth_enabled=True,
configurations=mock.MagicMock(),
client_cert_mode="Ignore",
https_only=True,
identity=None,
location="West Europe",
)
}
}
check = app_ensure_http_is_redirected_to_https()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"HTTP is redirected to HTTPS for app 'app_id-1' in subscription '{AZURE_SUBSCRIPTION_ID}'."
)
assert result[0].resource_name == "app_id-1"
assert result[0].resource_id == resource_id
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].location == "West Europe"
```
### Services
Coming soon ...
For testing Azure services, we have to follow the same logic as with the Azure checks. We still mock all the API calls, but in this case, every method that uses an API call to set up an attribute is mocked with the [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch) decorator at the beginning of the class. Remember that every method of a service MUST be tested.
The following code shows a real example of a testing class, but it has more comments than usual for educational purposes.
```python title="AppInsights Service Test"
# We need to import the unittest.mock.patch to allow us to patch some objects
# not to use shared ones between test, hence to isolate the test
from unittest.mock import patch
# Import the models needed from the service file
from prowler.providers.azure.services.appinsights.appinsights_service import (
AppInsights,
Component,
)
# Import some constans values needed in almost every check
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
# Function to mock the service function __get_components__, this function task is to return a possible value that real function could returns
def mock_appinsights_get_components(_):
return {
AZURE_SUBSCRIPTION_ID: {
"app_id-1": Component(
resource_id="/subscriptions/resource_id",
resource_name="AppInsightsTest",
location="westeurope",
)
}
}
# Patch decorator to use the mocked function instead the function with the real API call
@patch(
"prowler.providers.azure.services.appinsights.appinsights_service.AppInsights.__get_components__",
new=mock_appinsights_get_components,
)
class Test_AppInsights_Service:
# Mandatory test for every service, this method test the instance of the client is correct
def test__get_client__(self):
app_insights = AppInsights(set_mocked_azure_provider())
assert (
app_insights.clients[AZURE_SUBSCRIPTION_ID].__class__.__name__
== "ApplicationInsightsManagementClient"
)
# Second typical method that test if subscriptions is defined inside the client object
def test__get_subscriptions__(self):
app_insights = AppInsights(set_mocked_azure_provider())
assert app_insights.subscriptions.__class__.__name__ == "dict"
# Test for the function __get_components__, inside this client is used the mocked function
def test__get_components__(self):
appinsights = AppInsights(set_mocked_azure_provider())
assert len(appinsights.components) == 1
assert (
appinsights.components[AZURE_SUBSCRIPTION_ID]["app_id-1"].resource_id
== "/subscriptions/resource_id"
)
assert (
appinsights.components[AZURE_SUBSCRIPTION_ID]["app_id-1"].resource_name
== "AppInsightsTest"
)
assert (
appinsights.components[AZURE_SUBSCRIPTION_ID]["app_id-1"].location
== "westeurope"
)
```

View File

@@ -5,7 +5,7 @@ Prowler has been written in Python using the [AWS SDK (Boto3)](https://boto3.ama
Since Prowler uses AWS Credentials under the hood, you can follow any authentication method as described [here](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-precedence).
### AWS Authentication
### Authentication
Make sure you have properly configured your AWS-CLI with a valid Access Key and Region or declare AWS variables properly (or instance profile/role):
@@ -26,9 +26,8 @@ Those credentials must be associated to a user or role with proper permissions t
- `arn:aws:iam::aws:policy/SecurityAudit`
- `arn:aws:iam::aws:policy/job-function/ViewOnlyAccess`
> Moreover, some read-only additional permissions are needed for several checks, make sure you attach also the custom policy [prowler-additions-policy.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-additions-policy.json) to the role you are using.
> If you want Prowler to send findings to [AWS Security Hub](https://aws.amazon.com/security-hub), make sure you also attach the custom policy [prowler-security-hub.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-security-hub.json).
???+ note
Moreover, some read-only additional permissions are needed for several checks, make sure you attach also the custom policy [prowler-additions-policy.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-additions-policy.json) to the role you are using. If you want Prowler to send findings to [AWS Security Hub](https://aws.amazon.com/security-hub), make sure you also attach the custom policy [prowler-security-hub.json](https://github.com/prowler-cloud/prowler/blob/master/permissions/prowler-security-hub.json).
### Multi-Factor Authentication
@@ -39,7 +38,7 @@ If your IAM entity enforces MFA you can use `--mfa` and Prowler will ask you to
## Azure
Prowler for azure supports the following authentication types:
Prowler for Azure supports the following authentication types:
- Service principal authentication by environment variables (Enterprise Application)
- Current az cli credentials stored
@@ -63,33 +62,61 @@ The other three cases does not need additional configuration, `--az-cli-auth` an
### Permissions
To use each one you need to pass the proper flag to the execution. Prowler fro Azure handles two types of permission scopes, which are:
To use each one you need to pass the proper flag to the execution. Prowler for Azure handles two types of permission scopes, which are:
- **Azure Active Directory permissions**: Used to retrieve metadata from the identity assumed by Prowler and future AAD checks (not mandatory to have access to execute the tool)
- **Microsoft Entra ID permissions**: Used to retrieve metadata from the identity assumed by Prowler (not mandatory to have access to execute the tool).
- **Subscription scope permissions**: Required to launch the checks against your resources, mandatory to launch the tool.
#### Azure Active Directory scope
#### Microsoft Entra ID scope
Azure Active Directory (AAD) permissions required by the tool are the following:
Microsoft Entra ID (AAD earlier) permissions required by the tool are the following:
- `Directory.Read.All`
- `Policy.Read.All`
- `UserAuthenticationMethod.Read.All`
The best way to assign it is through the azure web console:
The best way to assign it is through the Azure web console:
1. Access to Microsoft Entra ID
2. In the left menu bar, go to "App registrations"
3. Once there, in the menu bar click on "+ New registration" to register a new application
4. Fill the "Name, select the "Supported account types" and click on "Register. You will be redirected to the applications page.
![Register an Application page](../img/register-application.png)
4. Select the new application
5. In the left menu bar, select "API permissions"
6. Then click on "+ Add a permission" and select "Microsoft Graph"
7. Once in the "Microsoft Graph" view, select "Application permissions"
8. Finally, search for "Directory", "Policy" and "UserAuthenticationMethod" select the following permissions:
- `Directory.Read.All`
- `Policy.Read.All`
- `UserAuthenticationMethod.Read.All`
![EntraID Permissions](../img/AAD-permissions.png)
![AAD Permissions](../img/AAD-permissions.png)
#### Subscriptions scope
Regarding the subscription scope, Prowler by default scans all the subscriptions that is able to list, so it is required to add the following RBAC builtin roles per subscription to the entity that is going to be assumed by the tool:
Regarding the subscription scope, Prowler by default scans all the subscriptions that is able to list, so it is required to add the following RBAC builtin roles per subscription to the entity that is going to be assumed by the tool:
- `Security Reader`
- `Reader`
To assign this roles, follow the instructions:
1. Access your subscription, then select your subscription.
2. Select "Access control (IAM)".
3. In the overview, select "Roles"
![IAM Page](../img/page-IAM.png)
4. Click on "+ Add" and select "Add role assignment"
5. In the search bar, type `Security Reader`, select it and click on "Next"
6. In the Members tab, click on "+ Select members" and add the members you want to assign this role.
7. Click on "Review + assign" to apply the new role.
*Repeat these steps for `Reader` role*
## Google Cloud
### GCP Authentication
### Authentication
Prowler will follow the same credentials search as [Google authentication libraries](https://cloud.google.com/docs/authentication/application-default-credentials#search_order):
@@ -97,10 +124,7 @@ Prowler will follow the same credentials search as [Google authentication librar
2. [User credentials set up by using the Google Cloud CLI](https://cloud.google.com/docs/authentication/application-default-credentials#personal)
3. [The attached service account, returned by the metadata server](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa)
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the following roles to the member associated with the credentials:
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the `Viewer` role to the member associated with the credentials.
- Viewer
- Security Reviewer
- Stackdriver Account Viewer
> By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.
???+ note
By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 338 KiB

BIN
docs/img/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 KiB

BIN
docs/img/page-IAM.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 240 KiB

View File

@@ -1,38 +1,25 @@
<p href="https://github.com/prowler-cloud/prowler">
<img align="right" src="./img/prowler-logo.png" height="100">
</p>
<br>
**Prowler** is an Open Source security tool to perform AWS, Azure, Google Cloud and Kubernetes security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness, and also remediations! We have Prowler CLI (Command Line Interface) that we call Prowler Open Source and a service on top of it that we call <a href="https://prowler.com">Prowler SaaS</a>.
# Prowler Documentation
## Prowler CLI
**Welcome to [Prowler Open Source v3](https://github.com/prowler-cloud/prowler/) Documentation!** 📄
```console
prowler <provider>
```
![Prowler CLI Execution](img/short-display.png)
For **Prowler v2 Documentation**, please go [here](https://github.com/prowler-cloud/prowler/tree/2.12.0) to the branch and its README.md.
## Prowler Dashboard
- You are currently in the **Getting Started** section where you can find general information and requirements to help you start with the tool.
- In the [Tutorials](./tutorials/misc.md) section you will see how to take advantage of all the features in Prowler.
- In the [Contact Us](./contact.md) section you can find how to reach us out in case of technical issues.
- In the [About](./about.md) section you will find more information about the Prowler team and license.
## About Prowler
**Prowler** is an Open Source security tool to perform AWS, Azure and Google Cloud security best practices assessments, audits, incident response, continuous monitoring, hardening and forensics readiness.
It contains hundreds of controls covering CIS, PCI-DSS, ISO27001, GDPR, HIPAA, FFIEC, SOC2, AWS FTR, ENS and custom security frameworks.
[![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/prowlercloud.svg?style=social&label=Follow%20%40prowlercloud)](https://twitter.com/prowlercloud)
## About ProwlerPro
<a href="https://prowler.pro"><img align="right" src="./img/prowler-pro-light.png" width="350"></a> **ProwlerPro** gives you the benefits of Prowler Open Source plus continuous monitoring, faster execution, personalized support, visualization of your data with dashboards, alerts and much more.
Visit <a href="https://prowler.pro">prowler.pro</a> for more info.
```console
prowler dashboard
```
![Prowler Dashboard](img/dashboard.png)
It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, AWS Well-Architected Framework Security Pillar, AWS Foundational Technical Review (FTR), ENS (Spanish National Security Scheme) and your custom security frameworks.
## Quick Start
### Installation
Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-cloud/), 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 using pip with `Python >= 3.9`:
=== "Generic"
@@ -40,7 +27,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
* `Python >= 3.9`
* `Python pip >= 3.9`
* AWS, GCP and/or Azure credentials
* AWS, GCP, Azure and/or Kubernetes credentials
_Commands_:
@@ -54,7 +41,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
_Requirements_:
* Have `docker` installed: https://docs.docker.com/get-docker/.
* AWS, GCP and/or Azure credentials
* AWS, GCP, Azure and/or Kubernetes credentials
* In the command below, change `-v` to your local directory path in order to access the reports.
_Commands_:
@@ -71,7 +58,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
_Requirements for Ubuntu 20.04.3 LTS_:
* AWS, GCP and/or Azure credentials
* 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`
@@ -91,7 +78,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
_Requirements for Developers_:
* AWS, GCP and/or Azure credentials
* AWS, GCP, Azure and/or Kubernetes credentials
* `git`, `Python >= 3.9`, `pip` and `poetry` installed (`pip install poetry`)
_Commands_:
@@ -108,7 +95,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
_Requirements_:
* AWS, GCP and/or Azure credentials
* 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`
@@ -125,7 +112,7 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
_Requirements_:
* `Brew` installed in your Mac or Linux
* AWS, GCP and/or Azure credentials
* AWS, GCP, Azure and/or Kubernetes credentials
_Commands_:
@@ -136,30 +123,25 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
=== "AWS CloudShell"
Prowler can be easely executed in AWS CloudShell but it has some prerequsites to be able to to so. AWS CloudShell is a container running with `Amazon Linux release 2 (Karoo)` that comes with Python 3.7, since Prowler requires Python >= 3.9 we need to first install a newer version of Python. Follow the steps below to successfully execute Prowler v3 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_:
* First install all dependences and then Python, in this case we need to compile it because there is not a package available at the time this document is written:
```
sudo yum -y install gcc openssl-devel bzip2-devel libffi-devel
wget https://www.python.org/ftp/python/3.9.16/Python-3.9.16.tgz
tar zxf Python-3.9.16.tgz
cd Python-3.9.16/
./configure --enable-optimizations
sudo make altinstall
python3.9 --version
cd
```
* Open AWS CloudShell `bash`.
_Commands_:
* Once Python 3.9 is available we can install Prowler from pip:
```
pip3.9 install prowler
prowler -v
sudo bash
adduser prowler
su prowler
pip install prowler
cd /tmp
prowler aws
```
> To download the results from AWS CloudShell, select Actions -> Download File and add the full path of each file. For the CSV file it will be something like `/home/cloudshell-user/output/prowler-output-123456789012-20221220191331.csv`
???+ note
To download the results from AWS CloudShell, select Actions -> Download File and add the full path of each file. For the CSV file it will be something like `/tmp/output/prowler-output-123456789012-20221220191331.csv`
=== "Azure CloudShell"
@@ -178,9 +160,11 @@ Prowler is available as a project in [PyPI](https://pypi.org/project/prowler-clo
The available versions of Prowler are the following:
- `latest`: in sync with master branch (bear in mind that it is not a stable version)
- `latest`: in sync with `master` branch (bear in mind that it is not a stable version)
- `v3-latest`: in sync with `v3` branch (bear in mind that it is not a stable version)
- `<x.y.z>` (release): you can find the releases [here](https://github.com/prowler-cloud/prowler/releases), those are stable releases.
- `stable`: this tag always point to the latest release.
- `v3-stable`: this tag always point to the latest release for v3.
The container images are available here:
@@ -189,21 +173,43 @@ The container images are available here:
## High level architecture
You can run Prowler from your workstation, an EC2 instance, Fargate or any other container, Codebuild, CloudShell, Cloud9 and many more.
You can run Prowler from your workstation, a Kubernetes Job, a Google Compute Engine, an Azure VM, an EC2 instance, Fargate or any other container, CloudShell and many more.
![Architecture](img/architecture.png)
## Deprecations from v3
### General
- `Allowlist` now is called `Mutelist`.
- The `--quiet` option has been deprecated, now use the `--status` flag to select the finding's status you want to get from PASS, FAIL or MANUAL.
- All `INFO` finding's status has changed to `MANUAL`.
- The CSV output format is common for all the providers.
We have deprecated some of our outputs formats:
- The HTML is replaced for the new Prowler Dashboard, run `prowler dashboard`.
- The native JSON is replaced for the JSON [OCSF](https://schema.ocsf.io/) v1.1.0, common for all the providers.
### AWS
- Deprecate the AWS flag --sts-endpoint-region since we use AWS STS regional tokens.
- To send only FAILS to AWS Security Hub, now use either `--send-sh-only-fails` or `--security-hub --status FAIL`.
## Basic Usage
To run Prowler, you will need to specify the provider (e.g aws, gcp or azure):
> If no provider specified, AWS will be used for backward compatibility with most of v2 options.
To run Prowler, you will need to specify the provider (e.g `aws`, `gcp`, `azure` or `kubernetes`):
???+ note
If no provider specified, AWS will be used for backward compatibility with most of v2 options.
```console
prowler <provider>
```
![Prowler Execution](img/short-display.png)
> Running the `prowler` command without options will use your environment variable credentials, see [Requirements](./getting-started/requirements.md) section to review the credentials settings.
If you miss the former output you can use `--verbose` but Prowler v3 is smoking fast, so you won't see much ;)
???+ note
Running the `prowler` command without options will use your environment variable credentials, see [Requirements](./getting-started/requirements.md) section to review the credentials settings.
If you miss the former output you can use `--verbose` but Prowler v4 is smoking fast, so you won't see much ;
By default, Prowler will generate a CSV, JSON and HTML reports, however you can generate a JSON-ASFF (used by AWS Security Hub) report with `-M` or `--output-modes`:
@@ -227,6 +233,7 @@ For executing specific checks or services you can use options `-c`/`checks` or `
prowler azure --checks storage_blob_public_access_level_is_disabled
prowler aws --services s3 ec2
prowler gcp --services iam compute
prowler kubernetes --services etcd apiserver
```
Also, checks and services can be excluded with options `-e`/`--excluded-checks` or `--excluded-services`:
@@ -235,6 +242,7 @@ Also, checks and services can be excluded with options `-e`/`--excluded-checks`
prowler aws --excluded-checks s3_bucket_public_access
prowler azure --excluded-services defender iam
prowler gcp --excluded-services kms
prowler kubernetes --excluded-services controllermanager
```
More options and executions methods that will save your time in [Miscellaneous](tutorials/misc.md).
@@ -252,9 +260,11 @@ Use a custom AWS profile with `-p`/`--profile` and/or AWS regions which you want
```console
prowler aws --profile custom-profile -f us-east-1 eu-south-2
```
> By default, `prowler` will scan all AWS regions.
See more details about AWS Authentication in [Requirements](getting-started/requirements.md)
???+ note
By default, `prowler` will scan all AWS regions.
See more details about AWS Authentication in [Requirements](getting-started/requirements.md#aws)
### Azure
@@ -274,7 +284,7 @@ prowler azure --browser-auth --tenant-id "XXXXXXXX"
prowler azure --managed-identity-auth
```
See more details about Azure Authentication in [Requirements](getting-started/requirements.md)
See more details about Azure Authentication in [Requirements](getting-started/requirements.md#azure)
Prowler by default scans all the subscriptions that is allowed to scan, if you want to scan a single subscription or various specific subscriptions you can use the following flag (using az cli auth as example):
```console
@@ -301,4 +311,28 @@ Prowler by default scans all the GCP Projects that is allowed to scan, if you wa
prowler gcp --project-ids <Project ID 1> <Project ID 2> ... <Project ID N>
```
See more details about GCP Authentication in [Requirements](getting-started/requirements.md)
See more details about GCP Authentication in [Requirements](getting-started/requirements.md#google-cloud)
### Kubernetes
Prowler allows you to scan your Kubernetes Cluster either from within the cluster or from outside the cluster.
For non in-cluster execution, you can provide the location of the KubeConfig file with the following argument:
```console
prowler kubernetes --kubeconfig-file path
```
For in-cluster execution, you can use the supplied yaml to run Prowler as a job:
```console
kubectl apply -f kubernetes/job.yaml
kubectl apply -f kubernetes/prowler-role.yaml
kubectl apply -f kubernetes/prowler-rolebinding.yaml
kubectl get pods --> prowler-XXXXX
kubectl logs prowler-XXXXX
```
> By default, `prowler` will scan all namespaces in your active Kubernetes context, use flag `--context` to specify the context to be scanned and `--namespaces` to specify the namespaces to be scanned.
## Prowler v2 Documentation
For **Prowler v2 Documentation**, please check it out [here](https://github.com/prowler-cloud/prowler/blob/8818f47333a0c1c1a457453c87af0ea5b89a385f/README.md).

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