diff --git a/.env b/.env new file mode 100644 index 0000000..b41de21 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +# Time window (UTC) +start_time=2026-01-08T00:00:00Z +end_time=2026-01-14T23:59:59Z diff --git a/.gitignore b/.gitignore index aeab419..c490d25 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ __pycache__/ /dist/ /build/ /.idea/ +och.json +lw_report_gen.log +samv.json diff --git a/Example-Report.html b/Example-Report.html deleted file mode 100644 index e6402bd..0000000 --- a/Example-Report.html +++ /dev/null @@ -1,1859 +0,0 @@ - - - - - - CSA Report - - - - - -
- - -
-
-
-

Assessment Report

-

Report created for ACME

-

Thursday April 24, 2025

-
- Generated by Fortinet -
- AWS Accounts Analyzed: 1, Azure Subscriptions Analyzed: 1, GCP Projects Analyzed: 1, Hosts Scanned: 11, Containers Scanned: 16 -
- -
- -
-
- -
-

Executive Summary

-

- The purpose of this report is to highlight the assessment findings for ACME. The findings below are - representative of the cloud accounts and hosts that were in scope of the engagement and cover cloud compliance - and vulnerability findings leveraging FortiCNAPP agentless scanning capabilities. This report provides a - detailed summary of each identified area of interest and how it pertains to your overall cloud security and - risk. -

-

- Below is a summary of findings. Additional detail is provided on subsequent pages: -

- -
-
-
-

Total Containers with Critical Vulnerabilities

-
-
- 8 -
-
- -
-
-

Hosts with Critical Vulnerabilities

-
-
- 2 -
-
- -
-
-

Total Critical AWS Compliance Findings

-
-
- 0 -
-
- - -
-
-

Total Critical Azure Compliance Findings

-
-
- 0 -
-
- - -
-
-

Total Critical GCP Compliance Findings

-
-
- 40 -
-
- -
-
-

Total High / Critical Behaviors Detected

-
-
- 2 -
-
- -
-
-

Number of Secrets Detected

-
-
- 1 -
-
-
-
-

- - This assessment offers a glimpse into the value that FortiCNAPP provides customers, including: - -

-

-

-

- -
-

Recommendations

-

- Based on the findings of this assessment, Fortinet recommends the following action plan and next steps: -

-
    -
  1. Engage with your Fortinet account team and partner to review services offerings to prioritize and remediate the findings
  2. -
  3. Complete a recurring Cloud Security Assessment once a wider FortiCNAPP deployment has been completed to baseline and trend improvements to your cloud security posture.
  4. -
-
- - -
-
-
- -

Exposed SSH Keys

-

- Using FortiCNAPP agentless workload scanning the following SSH Keys have been found on your workloads: -

- - - - - - - - - - - - - - - - -
HostnameFile PathSSH Key Type
ip-172-16-1-45.us-east-2.compute.internalhome/ubuntu/.ssh/id_frontendssh-rsa
- -
-
-

Compliance Findings

-

Using FortiCNAPP agentless compliance functionality, we’ve assessed the current security posture against best - practices, policies, and compliance frameworks. FortiCNAPP identified the following:

- -
-

AWS Compliance Findings

-

- Total AWS Accounts Analyzed: 1 - - - - - - - - - - - - - - - - - - - -
Account IDSeverity CountNon-compliant ResourcesTotal Assessed Resources
583683056848High: 877192
- - -

-
- -
- -

-

Azure Compliance Findings

- - Total Azure Subscriptions Analyzed: 1 - - - - - - - - - - - - - - - - - - -
Tenant IDSeverity CountNon-compliant ResourcesTotal Assessed Resources
a329d4bf-4557-4ccf-b132-84e7025ea22dHigh: 15114144
- - -

- -
- -
-

-

GCP Compliance Findings

- - Total GCP Subscriptions Analyzed: 1 - - - - - - - - - - - - - - - - - - -
Project IDSeverity CountNon-compliant ResourcesTotal Assessed Resources
lacework-demo-devCritical: 4 -High: 260116
- - -

-
-
-
- -

Behavioral & Known-Bad Alerts - Polygraph Anomalies (last 7 days / top 25)

-

- Using the FortiCNAPP agentless Cloud Log behavioral assessment & any behavioral data from any agents you may - have deployed, we’ve identified the following anomalous or policy-based activity for further investigation. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Alert IDSeverityAlert TimeAlert NameDescription
478601HighMarch 29, 2025 09:41PMPotentially Compromised HostHost machines may have been compromised. The following entities are suspected. Hosts: ip-10-0-2-5.us-east-2.compute.internal, ip-10-0-1-186.us-east-2.compute.internal.
442601HighMarch 22, 2025 09:17PMPotentially Compromised HostHost machines may have been compromised. The following entities are suspected. Hosts: ip-10-0-1-186.us-east-2.compute.internal, ip-10-0-2-5.us-east-2.compute.internal.
-
-
- -

Workload Vulnerability Assessment

-

- FortiCNAPP has scanned and identified vulnerable container images and/or hosts and associated risk of the - vulnerabilities present. If the FortiCNAPP agent was not installed as part of this assessment it may be - installed later to highlight observed behavior, communication paths, and context. -

- -

- Total Hosts Scanned: 11 -

- - - -

- Total Container Images Scanned: 16 -

-
- - - - - -

Host Vulnerability Summary

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SeverityTotal CVEsHosts Affected
Critical72
High17959
Medium2392911
Low00
- - -
- -

Container Vulnerability Summary

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SeverityTotal CVEsImages Affected
Critical1048
High48410
Medium63016
Low00
- - -
-
-

Appendix of Detailed Findings

-

This section contains additional details on the findings that were summarized above:

-

Detailed CVE Breakdown

-

Hosts With Critical, Fixable Vulnerabilities

-

This table lists all hosts with "critical" vulnerabilities that have fixes available. Additional - vulnerability information for other severity levels - can be found in the FortiCNAPP UI.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
HostnameCVESeverityPackage NameInstalled VersionFixed Version(s)
0ip-172-16-1-217.us-east-2.compute.internalCVE-2023-45133Criticalbabel-traverse6.26.07.23.2
1ip-172-16-1-217.us-east-2.compute.internalCVE-2021-44906Criticalminimist1.2.51.2.6
2ip-172-16-1-45.us-east-2.compute.internalCVE-2023-45133Criticalbabel-traverse6.26.07.23.2
3ip-172-16-1-45.us-east-2.compute.internalCVE-2021-44906Criticalminimist1.2.51.2.6
4ip-172-16-1-45.us-east-2.compute.internalCVE-2021-44228Criticalorg.apache.logging.log4j:log4j-core2.6.12.15.0
5ip-172-16-1-45.us-east-2.compute.internalCVE-2021-45046Criticalorg.apache.logging.log4j:log4j-core2.6.12.16.0
6ip-172-16-1-45.us-east-2.compute.internalCVE-2017-5645Criticalorg.apache.logging.log4j:log4j-core2.6.12.8.2
- - - -

Containers With Critical, Fixable Vulnerabilities

-

This table lists all containers with "critical" vulnerabilities that have fixes available. Additional - vulnerability information can be found in the FortiCNAPP UI.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RepositoryImage IDCVESeverityPackage NameInstalled VersionFixed Version(s)
0detcaccounts/ecommerce-inventorysha256:e01e3ff828c30b87203fbcf381272e6c505dcfaa436d585fba5b04aec7ab9bd0CVE-2024-32002Criticalgit1:2.20.1-2+deb10u81:2.20.1-2+deb10u9
1detcaccounts/ecommerce-inventorysha256:e01e3ff828c30b87203fbcf381272e6c505dcfaa436d585fba5b04aec7ab9bd0CVE-2023-38408Criticalopenssh1:7.9p1-10+deb10u21:7.9p1-10+deb10u3
2detcaccounts/ecommerce-inventorysha256:e01e3ff828c30b87203fbcf381272e6c505dcfaa436d585fba5b04aec7ab9bd0CVE-2022-1586, CVE-2022-1587Criticalpcre210.32-510.32-5+deb10u1
3detcaccounts/ecommerce-inventorysha256:e01e3ff828c30b87203fbcf381272e6c505dcfaa436d585fba5b04aec7ab9bd0CVE-2021-3177Criticalpython2.72.7.16-2+deb10u12.7.16-2+deb10u2
4detcaccounts/ecommerce-inventorysha256:e01e3ff828c30b87203fbcf381272e6c505dcfaa436d585fba5b04aec7ab9bd0CVE-2022-48565Criticalpython2.72.7.16-2+deb10u12.7.16-2+deb10u3
5detcaccounts/ecommerce-inventorysha256:e01e3ff828c30b87203fbcf381272e6c505dcfaa436d585fba5b04aec7ab9bd0CVE-2022-48565Criticalpython3.73.7.3-2+deb10u43.7.3-2+deb10u6
6detcaccounts/ecommerce-loginsha256:53955dfb22799ff8c62067e429ceba9856864a48c66fad6c591f4f4316b538cfCVE-2024-32002Criticalgit1:2.20.1-2+deb10u81:2.20.1-2+deb10u9
7detcaccounts/ecommerce-loginsha256:53955dfb22799ff8c62067e429ceba9856864a48c66fad6c591f4f4316b538cfCVE-2023-38408Criticalopenssh1:7.9p1-10+deb10u21:7.9p1-10+deb10u3
8detcaccounts/ecommerce-loginsha256:53955dfb22799ff8c62067e429ceba9856864a48c66fad6c591f4f4316b538cfCVE-2022-1586, CVE-2022-1587Criticalpcre210.32-510.32-5+deb10u1
9detcaccounts/ecommerce-loginsha256:53955dfb22799ff8c62067e429ceba9856864a48c66fad6c591f4f4316b538cfCVE-2021-3177Criticalpython2.72.7.16-2+deb10u12.7.16-2+deb10u2
10detcaccounts/ecommerce-loginsha256:53955dfb22799ff8c62067e429ceba9856864a48c66fad6c591f4f4316b538cfCVE-2022-48565Criticalpython2.72.7.16-2+deb10u12.7.16-2+deb10u3
11detcaccounts/ecommerce-loginsha256:53955dfb22799ff8c62067e429ceba9856864a48c66fad6c591f4f4316b538cfCVE-2022-48565Criticalpython3.73.7.3-2+deb10u43.7.3-2+deb10u6
12detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2024-32002Criticalgit1:2.20.1-2+deb10u81:2.20.1-2+deb10u9
13detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2024-21508Criticalmysql22.3.33.9.4
14detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2024-21511Criticalmysql22.3.33.9.7
15detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2023-38408Criticalopenssh1:7.9p1-10+deb10u21:7.9p1-10+deb10u3
16detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2022-1586, CVE-2022-1587Criticalpcre210.32-510.32-5+deb10u1
17detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2021-3177Criticalpython2.72.7.16-2+deb10u12.7.16-2+deb10u2
18detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2022-48565Criticalpython2.72.7.16-2+deb10u12.7.16-2+deb10u3
19detcaccounts/ecommerce-ordersha256:39294e42ebc94c2a3ac0da99d351f90a5878d97b20e050bcfea134f5ff77df0dCVE-2022-48565Criticalpython3.73.7.3-2+deb10u43.7.3-2+deb10u6
20detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2017-1000257Criticalcurl7.52.1-r27.56.1-r0
21detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2017-8816, CVE-2017-8817, CVE-2017-8818Criticalcurl7.52.1-r27.57.0-r0
22detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-1000005Criticalcurl7.52.1-r27.58.0-r0
23detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-1000120, CVE-2018-1000122Criticalcurl7.52.1-r27.59.0-r0
24detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-1000300, CVE-2018-1000301Criticalcurl7.52.1-r27.60.0-r0
25detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-0500Criticalcurl7.52.1-r27.61.0-r0
26detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-14618Criticalcurl7.52.1-r27.61.1-r0
27detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-16839, CVE-2018-16840, CVE-2018-16842Criticalcurl7.52.1-r27.61.1-r1
28detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2017-8105, CVE-2017-8287Criticalfreetype2.7-r02.7.1-r1
29detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-14599, CVE-2018-14600Criticallibx111.6.4-r01.6.6-r0
30detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-2938Criticalopenjdk88.121.13-r08.181.13-r0
31detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2018-3183Criticalopenjdk88.121.13-r08.191.12-r0
32detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2021-44228Criticalorg.apache.logging.log4j:log4j-core2.6.12.15.0
33detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2021-45046Criticalorg.apache.logging.log4j:log4j-core2.6.12.16.0
34detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2017-5645Criticalorg.apache.logging.log4j:log4j-core2.6.12.8.2
35detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2022-22963Criticalorg.springframework.cloud:spring-cloud-function-context3.2.23.2.3
36detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2022-22963Criticalorg.springframework.cloud:spring-cloud-function-core3.2.23.2.3
37detcaccounts/ecommerce-websitesha256:7aba286656dc7d4b7529ec75090a117f1454fbf28bd8195848da6a69c74338faCVE-2016-9841, CVE-2016-9843Criticalzlib1.2.8-r21.2.11-r0
38detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2022-1664Criticaldpkg1.18.251.18.26
39detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2021-3918Criticaljson-schema0.2.30.4.0
40detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2021-3520Criticallz40.0~r131-20.0~r131-2+deb9u1
41detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2021-44906Criticalminimist1.2.51.2.6
42detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2022-29155Criticalopenldap2.4.44+dfsg-5+deb9u82.4.44+dfsg-5+deb9u9
43detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2022-2421Criticalsocket.io-parser3.3.23.3.3
44detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2022-2421Criticalsocket.io-parser3.4.13.4.2
45detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2021-31597Criticalxmlhttprequest-ssl1.5.51.6.1
46detcaccounts/voteapp-results-sitesha256:1c27e07fb052c879bf0237ad0d6bc84ce4084d0a3fa7377fb13f325e95798e80CVE-2020-28502Criticalxmlhttprequest-ssl1.5.51.6.2
47detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2023-38545Criticalcurl7.74.0-1.3+deb11u17.74.0-1.3+deb11u10
48detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2021-22945, CVE-2022-32207Criticalcurl7.74.0-1.3+deb11u17.74.0-1.3+deb11u2
49detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2022-32221Criticalcurl7.74.0-1.3+deb11u17.74.0-1.3+deb11u5
50detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2022-1664Criticaldpkg1.20.91.20.10
51detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2024-45491, CVE-2024-45492Criticalexpat2.2.10-2+deb11u32.2.10-2+deb11u6
52detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2024-37371Criticalkrb51.18.3-6+deb11u11.18.3-6+deb11u5
53detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2021-46848Criticallibtasn1-64.16.0-24.16.0-2+deb11u1
54detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2022-29155Criticalopenldap2.4.57+dfsg-32.4.57+dfsg-3+deb11u1
55detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2022-1292Criticalopenssl1.1.1n-0+deb11u11.1.1n-0+deb11u2
56detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2022-2068Criticalopenssl1.1.1n-0+deb11u11.1.1n-0+deb11u3
57detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2024-5535Criticalopenssl1.1.1n-0+deb11u11.1.1w-0+deb11u2
58detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2022-1586, CVE-2022-1587Criticalpcre210.36-210.36-2+deb11u1
59detcaccounts/voteapp-websitesha256:a94d80f1fd196a58a99957a2c7b6f6b387fe88a32b76d1a0b696219bc7e5ac14CVE-2022-37434Criticalzlib1:1.2.11.dfsg-2+deb11u11:1.2.11.dfsg-2+deb11u2
60library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2022-23806Criticalgo-compiler1.16.71.16.14
61library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2024-37371Criticalkrb51.18.3-6+deb11u11.18.3-6+deb11u5
62library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2022-3515Criticallibksba1.5.0-31.5.0-3+deb11u1
63library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2022-47629Criticallibksba1.5.0-31.5.0-3+deb11u2
64library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2021-46848Criticallibtasn1-64.16.0-24.16.0-2+deb11u1
65library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2024-5535Criticalopenssl1.1.1n-0+deb11u31.1.1w-0+deb11u2
66library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2022-1586, CVE-2022-1587Criticalpcre210.36-210.36-2+deb11u1
67library/postgressha256:e09e90144645e02137d087f0dc059f4d2e3c6356ef8f9e40eeb15d1c901dbc73CVE-2022-37434Criticalzlib1:1.2.11.dfsg-2+deb11u11:1.2.11.dfsg-2+deb11u2
- - -
- -

Detailed Cloud Compliance Findings

-

-

AWS - Top High/Critical Compliance Findings

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account IDCategoryTitleSeverityResources
0583683056848NetworkingEnsure no Network Access Control Lists (ACL) allow ingress from 0.0.0.0/0 to remote server administration portsHigh21 / 26
1583683056848NetworkingEnsure the default security group of every Virtual Private Cloud (VPC) restricts all trafficHigh21 / 42
2583683056848LoggingEnable AWS Config in all regionsHigh17 / 17
3583683056848Identity and Access ManagementEnsure Identity and Access Management (IAM) policies that allow full "*:*" administrative privileges are not attached to rolesHigh10 / 60
4583683056848NetworkingEnsure no security groups allow ingress from 0.0.0.0/0 to remote server administration portsHigh4 / 42
5583683056848Identity and Access ManagementEnsure Identity and Access Management (IAM) policies that allow full "*:*" administrative privileges are not attached to usersHigh2 / 3
6583683056848Storage RDSEnable encryption for Relational Database Service (RDS) InstancesHigh1 / 1
7583683056848LoggingEnsure AWS Config is recording Global Resources in at least one regionHigh1 / 1
- - -

Azure - Top High/Critical Compliance Findings

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Tenant IDCategoryTitleSeverityResources
a329d4bf-4557-4ccf-b132-84e7025ea22dNetworkingEnsure that Network Watcher is 'Enabled' (includes Reserved access regions)High55 / 58
a329d4bf-4557-4ccf-b132-84e7025ea22dNetworkingEnsure that Network Watcher is 'Enabled' (excludes Reserved access regions)High41 / 58
a329d4bf-4557-4ccf-b132-84e7025ea22dVirtual MachinesEncrypt 'OS and Data' disks with Customer Managed Key (CMK)High4 / 4
a329d4bf-4557-4ccf-b132-84e7025ea22dNetworkingEvaluate and restrict SSH access from the InternetHigh2 / 5
a329d4bf-4557-4ccf-b132-84e7025ea22dNetworkingEvaluate and restrict HTTP(S) access from the InternetHigh2 / 5
a329d4bf-4557-4ccf-b132-84e7025ea22dStorage AccountsSet Default Network Access Rule for Storage Accounts to DenyHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Create or Update Network Security GroupHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Delete Network Security GroupHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Create or Update Security SolutionHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Delete Security SolutionHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Create or Update SQL Server Firewall RuleHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Delete SQL Server Firewall RuleHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Create or Update Public IP Address ruleHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dLogging and MonitoringEnsure that Activity Log Alert exists for Delete Public IP Address ruleHigh1 / 1
a329d4bf-4557-4ccf-b132-84e7025ea22dNetworkingEvaluate and restrict Remote Desktop Protocol (RDP) access from the InternetHigh1 / 5
- - -

GCP - Top High/Critical Compliance Findings

-

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project IDCategoryTitleSeverityResources
lacework-demo-devVirtual MachinesEncrypt VM Disks for Critical VMs With Customer-Supplied Encryption Keys (CSEK)Critical18 / 18
lacework-demo-devVirtual MachinesEncrypt VM Disks for Critical VMs With Customer-Supplied Encryption Keys (CSEK)Critical18 / 18
lacework-demo-devNetworkingRestrict Remote Desktop Protocol (RDP) Access From the InternetCritical2 / 24
lacework-demo-devNetworkingRestrict Remote Desktop Protocol (RDP) Access From the InternetCritical2 / 24
lacework-demo-devVirtual MachinesEnsure That Compute Instances Do Not Have Public IP AddressesHigh10 / 16
lacework-demo-devVirtual MachinesEnsure That Compute Instances Do Not Have Public IP AddressesHigh10 / 16
- -

GCP - Critical Findings with Details

-

This table contains CIS compliance findings with a severity of "Critical". Other severity levels can be - reviewed in the FortiCNAPP UI.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Project IDCategoryControlViolations
42lacework-demo-devNetworkingRestrict Remote Desktop Protocol (RDP) Access From the InternetRegion:global
Resource: //compute.googleapis.com/projects/lacework-demo-dev/global/firewalls/default-allow-rdp
Reasons: ['RDPAccessAllowed']

Region:global
Resource: //compute.googleapis.com/projects/lacework-demo-dev/global/firewalls/default-allow-rdp
Reasons: ['RDPAccessAllowed']

54lacework-demo-devVirtual MachinesEncrypt VM Disks for Critical VMs With Customer-Supplied Encryption Keys (CSEK)Region:us-central1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-central1-c/disks/disk-clone-xaccount
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-central1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-central1-c/disks/disk-clone-xaccount
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/activity-generator
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/activity-generator
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer0
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer0
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer1
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer1
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/gke-sharedgke-default-node-pool-f1d63e9e-pqrt
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/gke-sharedgke-default-node-pool-f1d63e9e-pqrt
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/mongodb
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/mongodb
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/ticketing-utilty
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/ticketing-utilty
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-c/disks/gke-sharedgke-default-node-pool-da487fab-1wjx
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-c/disks/gke-sharedgke-default-node-pool-da487fab-1wjx
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-d
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-d/disks/gke-sharedgke-default-node-pool-0899760d-hjvn
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-d
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-d/disks/gke-sharedgke-default-node-pool-0899760d-hjvn
Reasons: ['DiskNotEncryptedWithCSEK']

130lacework-demo-devNetworkingRestrict Remote Desktop Protocol (RDP) Access From the InternetRegion:global
Resource: //compute.googleapis.com/projects/lacework-demo-dev/global/firewalls/default-allow-rdp
Reasons: ['RDPAccessAllowed']

Region:global
Resource: //compute.googleapis.com/projects/lacework-demo-dev/global/firewalls/default-allow-rdp
Reasons: ['RDPAccessAllowed']

142lacework-demo-devVirtual MachinesEncrypt VM Disks for Critical VMs With Customer-Supplied Encryption Keys (CSEK)Region:us-central1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-central1-c/disks/disk-clone-xaccount
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-central1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-central1-c/disks/disk-clone-xaccount
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/activity-generator
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/activity-generator
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer0
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer0
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer1
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/datalayer1
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/gke-sharedgke-default-node-pool-f1d63e9e-pqrt
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/gke-sharedgke-default-node-pool-f1d63e9e-pqrt
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/mongodb
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/mongodb
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/ticketing-utilty
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-b
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-b/disks/ticketing-utilty
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-c/disks/gke-sharedgke-default-node-pool-da487fab-1wjx
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-c
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-c/disks/gke-sharedgke-default-node-pool-da487fab-1wjx
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-d
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-d/disks/gke-sharedgke-default-node-pool-0899760d-hjvn
Reasons: ['DiskNotEncryptedWithCSEK']

Region:us-east1-d
Resource: //compute.googleapis.com/projects/lacework-demo-dev/zones/us-east1-d/disks/gke-sharedgke-default-node-pool-0899760d-hjvn
Reasons: ['DiskNotEncryptedWithCSEK']

- -
-
- - - \ No newline at end of file diff --git a/Example-Report.pdf b/Example-Report.pdf deleted file mode 100644 index 64ae3be..0000000 Binary files a/Example-Report.pdf and /dev/null differ diff --git a/assets/CloudSecPosture.png b/assets/CloudSecPosture.png new file mode 100644 index 0000000..8cd81fa Binary files /dev/null and b/assets/CloudSecPosture.png differ diff --git a/assets/CloudStatusQuo.png b/assets/CloudStatusQuo.png new file mode 100644 index 0000000..ffc72dd Binary files /dev/null and b/assets/CloudStatusQuo.png differ diff --git a/assets/FortiCNAPP-Unified-Approach.png b/assets/FortiCNAPP-Unified-Approach.png index 5bf3fee..0d63ef9 100644 Binary files a/assets/FortiCNAPP-Unified-Approach.png and b/assets/FortiCNAPP-Unified-Approach.png differ diff --git a/assets/FortiCNAPP.png b/assets/FortiCNAPP.png new file mode 100644 index 0000000..a555dac Binary files /dev/null and b/assets/FortiCNAPP.png differ diff --git a/assets/FortinetSecFabric.png b/assets/FortinetSecFabric.png new file mode 100644 index 0000000..760a234 Binary files /dev/null and b/assets/FortinetSecFabric.png differ diff --git a/assets/StateofCloud_SecReport.png b/assets/StateofCloud_SecReport.png new file mode 100644 index 0000000..64459a9 Binary files /dev/null and b/assets/StateofCloud_SecReport.png differ diff --git a/assets/sec_outcomes.png b/assets/sec_outcomes.png new file mode 100644 index 0000000..a232917 Binary files /dev/null and b/assets/sec_outcomes.png differ diff --git a/ciem.sh b/ciem.sh new file mode 100755 index 0000000..df93091 --- /dev/null +++ b/ciem.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +# Lacework API Query Script + +set -e # Exit on any error + +# Configuration +API_KEY="" +KEY_ID="" +BASE_URL="" +ID_HIGH_PRIV="Identities_with_excessive_privileges.json" +ID_ROOT="Root_Identities.json" +ID_ROOTandHIGH="Root_Identities_with_excessive_privileges.json" +OUTPUT_TABLE="Root_Identities_with_excessive_privileges_table.txt" + +echo "Starting Lacework API query..." + +# Step 1: Get access token +echo "Getting access token..." +TOKEN_RESPONSE=$(curl -s -H "X-LW-UAKS:${API_KEY}" \ + -H "Content-Type: application/json" \ + -X POST \ + -d "{\"keyId\": \"${KEY_ID}\", \"expiryTime\": 3600}" \ + "${BASE_URL}/access/tokens") + +# Extract token from response +ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token // .access_token // .data.token') + +if [ "$ACCESS_TOKEN" == "null" ] || [ -z "$ACCESS_TOKEN" ]; then + echo "Error: Failed to get access token" + echo "Response: $TOKEN_RESPONSE" + exit 1 +fi + +echo "Access token obtained successfully" + +# Step 2: Execute query for AWS identities with high unused entitlements +echo "Executing query for identities with excessive privileges..." +curl -s -X POST "${BASE_URL}/Queries/execute" \ + -H "Authorization: ${ACCESS_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "query": { + "queryText": "{\n source {\n LW_CE_IDENTITIES\n }\n filter {\n PROVIDER_TYPE = '\''AWS'\''\n }\n return distinct {\n RECORD_CREATED_TIME,\n PRINCIPAL_ID,\n PROVIDER_TYPE,\n DOMAIN_ID,\n NAME,\n LAST_USED_TIME,\n CREATED_TIME,\n METRICS,\n TAGS,\n ACCESS_KEYS,\n ACCESS_KEYS_LIST,\n ENTITLEMENT_COUNTS\n }\n}" + }, + "arguments": [ + { + "name": "StartTimeRange", + "value": "2024-01-01T00:00:00.000Z" + }, + { + "name": "EndTimeRange", + "value": "2024-12-31T23:59:59.000Z" + } + ] + }' | jq '.data[] | select(.ENTITLEMENT_COUNTS.entitlements_unused_count >= 70)' > "$ID_HIGH_PRIV" + +# Step 3: Execute query for AWS identities with full admin access +echo "Executing query for root identities..." +curl -s -X POST "${BASE_URL}/Queries/execute" \ + -H "Authorization: ${ACCESS_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "query": { + "queryText": "{\n source {\n LW_CE_IDENTITIES\n }\n filter {\n PROVIDER_TYPE = '\''AWS'\''\n }\n return distinct {\n RECORD_CREATED_TIME,\n PRINCIPAL_ID,\n PROVIDER_TYPE,\n DOMAIN_ID,\n NAME,\n LAST_USED_TIME,\n CREATED_TIME,\n METRICS,\n TAGS,\n ACCESS_KEYS,\n ACCESS_KEYS_LIST,\n ENTITLEMENT_COUNTS\n }\n}" + }, + "arguments": [ + { + "name": "StartTimeRange", + "value": "2024-01-01T00:00:00.000Z" + }, + { + "name": "EndTimeRange", + "value": "2024-12-31T23:59:59.000Z" + } + ] + }' | jq '.data[] | select(.METRICS.risks[]? == "ALLOWS_FULL_ADMIN")' > "$ID_ROOT" + +# Step 4: Execute query for AWS identities with high unused entitlements and full admin access +echo "Executing query for root identities with excessive privileges..." +curl -s -X POST "${BASE_URL}/Queries/execute" \ + -H "Authorization: ${ACCESS_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "query": { + "queryText": "{\n source {\n LW_CE_IDENTITIES\n }\n filter {\n PROVIDER_TYPE = '\''AWS'\''\n }\n return distinct {\n RECORD_CREATED_TIME,\n PRINCIPAL_ID,\n PROVIDER_TYPE,\n DOMAIN_ID,\n NAME,\n LAST_USED_TIME,\n CREATED_TIME,\n METRICS,\n TAGS,\n ACCESS_KEYS,\n ACCESS_KEYS_LIST,\n ENTITLEMENT_COUNTS\n }\n}" + }, + "arguments": [ + { + "name": "StartTimeRange", + "value": "2024-01-01T00:00:00.000Z" + }, + { + "name": "EndTimeRange", + "value": "2024-12-31T23:59:59.000Z" + } + ] + }' | jq '.data[] | select(.ENTITLEMENT_COUNTS.entitlements_unused_count >= 70) | select(.METRICS.risks[]? == "ALLOWS_FULL_ADMIN")' > "$ID_ROOTandHIGH" + +# Step 5: Create a formatted table output +echo "Creating formatted table output..." +curl -s -X POST "${BASE_URL}/Queries/execute" \ + -H "Authorization: ${ACCESS_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "query": { + "queryText": "{\n source {\n LW_CE_IDENTITIES\n }\n filter {\n PROVIDER_TYPE = '\''AWS'\''\n }\n return distinct {\n RECORD_CREATED_TIME,\n PRINCIPAL_ID,\n PROVIDER_TYPE,\n DOMAIN_ID,\n NAME,\n LAST_USED_TIME,\n CREATED_TIME,\n METRICS,\n TAGS,\n ACCESS_KEYS,\n ACCESS_KEYS_LIST,\n ENTITLEMENT_COUNTS\n }\n}" + }, + "arguments": [ + { + "name": "StartTimeRange", + "value": "2024-01-01T00:00:00.000Z" + }, + { + "name": "EndTimeRange", + "value": "2024-12-31T23:59:59.000Z" + } + ] + }' | jq -r '.data[] | select(.ENTITLEMENT_COUNTS.entitlements_unused_count >= 70) | select(.METRICS.risks[]? == "ALLOWS_FULL_ADMIN") | [.PRINCIPAL_ID, .NAME, .PROVIDER_TYPE, .DOMAIN_ID, (.ENTITLEMENT_COUNTS.entitlements_unused_count | tostring), .METRICS.risk_severity] | @tsv' | column -t -s $'\t' > "$OUTPUT_TABLE" + +# Check results and display summary +echo "" +echo "=== RESULTS SUMMARY ===" + +if [ -f "$ID_HIGH_PRIV" ]; then + HIGH_PRIV_COUNT=$(jq -s 'length' "$ID_HIGH_PRIV") + echo "Identities with >= 70 unused entitlements: $HIGH_PRIV_COUNT" + echo "Results saved to: $ID_HIGH_PRIV" +else + echo "Error: High privilege identities file was not created" +fi + +if [ -f "$ID_ROOT" ]; then + ROOT_COUNT=$(jq -s 'length' "$ID_ROOT") + echo "Identities with ALLOWS_FULL_ADMIN: $ROOT_COUNT" + echo "Results saved to: $ID_ROOT" +else + echo "Error: Root identities file was not created" +fi + +if [ -f "$ID_ROOTandHIGH" ]; then + COMBINED_COUNT=$(jq -s 'length' "$ID_ROOTandHIGH") + echo "Identities with >= 70 unused entitlements AND full admin access: $COMBINED_COUNT" + echo "Results saved to: $ID_ROOTandHIGH" + echo "Formatted table saved to: $OUTPUT_TABLE" +else + echo "Error: Combined results file was not created" +fi + +echo "" +echo "Query execution completed!" diff --git a/lw_report_gen.py b/lw_report_gen.py index 4b06775..3549a8f 100644 --- a/lw_report_gen.py +++ b/lw_report_gen.py @@ -58,6 +58,7 @@ def main(): vulns_end_time=pre_processed_args['vulns_end_time'], alerts_start_time=pre_processed_args['alerts_start_time'], alerts_end_time=pre_processed_args['alerts_end_time'], + ciem_threshold=args.ciem_threshold, custom_logo=custom_logo ) elif args.report_format == "PDF": @@ -68,6 +69,7 @@ def main(): vulns_end_time=pre_processed_args['vulns_end_time'], alerts_start_time=pre_processed_args['alerts_start_time'], alerts_end_time=pre_processed_args['alerts_end_time'], + ciem_threshold=args.ciem_threshold, custom_logo=custom_logo, pagesize='a2', pdf=True, diff --git a/modules/compliance.py b/modules/compliance.py index 8ae8166..2acc163 100644 --- a/modules/compliance.py +++ b/modules/compliance.py @@ -21,8 +21,8 @@ def __init__(self, data: dict): self.account_id_string = 'ACCOUNT_ID' self.account_id_rename_string = 'Account ID' if self.cloud_provider == 'AZURE': - self.account_id_string = 'TENANT_ID' - self.account_id_rename_string = 'Tenant ID' + self.account_id_string = 'SUBSCRIPTION_ID' + self.account_id_rename_string = 'Subscription ID' if self.cloud_provider == 'GCP': self.account_id_string = 'PROJECT_ID' self.account_id_rename_string = 'Project ID' @@ -40,6 +40,42 @@ def get_total_accounts_evaluated(self): unique_accounts = df[self.account_id_string].nunique() return unique_accounts + def get_unique_critical_finding_count(self): + """ + Count unique critical compliance findings across all accounts. + If the same control fails in multiple accounts, it's only counted once. + + Returns: + Integer count of unique critical findings + """ + df = pd.DataFrame(self.all_recommendations) + df = df[df['STATUS'].isin(["NonCompliant"])] + df = df[df['SEVERITY'] == 1] # Critical severity + + # Count unique TITLE values (unique compliance controls) + unique_critical_count = df['TITLE'].nunique() + return unique_critical_count + + def get_unique_finding_count(self, severities=["Critical", "High"]): + """ + Count unique compliance findings across all accounts for specified severities. + If the same control fails in multiple accounts, it's only counted once. + + Args: + severities: List of severity strings to include + + Returns: + Integer count of unique findings + """ + df = pd.DataFrame(self.all_recommendations) + df = df[df['STATUS'].isin(["NonCompliant"])] + df = df.replace({'SEVERITY': {1: "Critical", 2: "High", 3: "Medium", 4: "Low", 5: "Info"}}) + df = df[df['SEVERITY'].isin(severities)] + + # Count unique TITLE values (unique compliance controls) + unique_finding_count = df['TITLE'].nunique() + return unique_finding_count + def get_compliance_details(self, severities=["Critical", "High"]): df = pd.DataFrame(self.all_recommendations) df = df[df['STATUS'].isin(["NonCompliant"])] @@ -161,42 +197,122 @@ def get_summary_by_service(self, severities=["Critical", "High", "Medium", "Low" def get_summary_by_account_bar_graph(self, width=600, height=350, format='svg'): df = self.get_summary_by_account() + # Modern color palette matching severity levels colors = [ - 'crimson', - 'darkorange', - 'gold', - 'lightskyblue', - 'powderblue' + '#DC2626', # Modern red for Critical + '#F97316', # Vibrant orange for High + '#FBBF24', # Warm yellow for Medium + '#3B82F6', # Clean blue for Low + '#6B7280' # Gray for Info ] unique_accounts = len(df.index) - # acct_id, criticals, highs, mediums, lows, infos if unique_accounts == 1: - fig = go.Figure(go.Bar(name="asdf", x=df.columns, y=df.iloc[0], marker_color=colors)) + fig = go.Figure(go.Bar( + x=df.columns, + y=df.iloc[0], + marker=dict( + color=colors[:len(df.columns)], + line=dict(color='rgba(255, 255, 255, 0.8)', width=1.5), + opacity=0.9 + ), + text=df.iloc[0], + textposition='outside', + textfont=dict(size=12, color='#1F2937', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + hovertemplate='%{x}
Failed Resources: %{y}' + )) fig.update_layout( - title='Compliance Severities Found', + title=dict( + text='Compliance Findings by Severity', + font=dict(size=18, color='#111827', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', weight=600), + x=0.5, + xanchor='center' + ), yaxis=dict( - title='Failed resources' - ) + title='Failed Resources', + titlefont=dict(size=14, color='#4B5563', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + tickfont=dict(size=12, color='#6B7280'), + gridcolor='#E5E7EB', + gridwidth=1, + showgrid=True, + zeroline=False + ), + xaxis=dict( + tickfont=dict(size=12, color='#6B7280', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + showgrid=False + ), + plot_bgcolor='rgba(249, 250, 251, 0.5)', + paper_bgcolor='white', + margin=dict(l=60, r=40, t=100, b=60), + font=dict(family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + uniformtext=dict(mode='hide', minsize=8) ) else: severities = df.columns graph_data = [] for idx, sev in enumerate(severities): - bar = go.Bar(name=sev, x=df.index, y=df[sev], marker_color=colors[idx]) + bar = go.Bar( + name=sev, + x=df.index, + y=df[sev], + marker=dict( + color=colors[idx] if idx < len(colors) else colors[-1], + line=dict(color='rgba(255, 255, 255, 0.6)', width=1), + opacity=0.9 + ), + text=df[sev], + textposition='inside', + textfont=dict(size=11, color='white', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + hovertemplate='%{x}
' + sev + ': %{y}' + ) graph_data.append(bar) - fig = go.Figure( - data=graph_data[::-1] - ) + fig = go.Figure(data=graph_data[::-1]) fig.update_layout( - title='Compliance Severities by Account', + title=dict( + text='Compliance Findings by Account', + font=dict(size=18, color='#111827', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', weight=600), + x=0.5, + xanchor='center' + ), yaxis=dict( - title='Failed resources' + title='Failed Resources', + titlefont=dict(size=14, color='#4B5563', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + tickfont=dict(size=12, color='#6B7280'), + gridcolor='#E5E7EB', + gridwidth=1, + showgrid=True, + zeroline=False + ), + xaxis=dict( + tickfont=dict(size=12, color='#6B7280', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + showgrid=False + ), + barmode='stack', + plot_bgcolor='rgba(249, 250, 251, 0.5)', + paper_bgcolor='white', + margin=dict(l=60, r=40, t=120, b=60), + font=dict(family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + legend=dict( + orientation='h', + yanchor='bottom', + y=1.02, + xanchor='center', + x=0.5, + bgcolor='rgba(255, 255, 255, 0.8)', + bordercolor='#E5E7EB', + borderwidth=1, + font=dict(size=12, color='#374151') + ), + hoverlabel=dict( + bgcolor='white', + font_size=13, + font_family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + bordercolor='#E5E7EB' ), - barmode='stack' + uniformtext=dict(mode='hide', minsize=8) ) img_bytes = fig.to_image(format=format, width=width, height=height) @@ -204,28 +320,80 @@ def get_summary_by_account_bar_graph(self, width=600, height=350, format='svg'): def get_summary_by_service_bar_graph(self, width=600, height=350, format='svg'): df = self.get_summary_by_service() - # colors = [ - # 'crimson', - # 'darkorange', - # 'gold', - # 'lightskyblue', - # 'powderblue' - # ] + + # Modern color palette for accounts/tenants/projects + account_colors = [ + '#0EA5E9', '#8B5CF6', '#EC4899', '#F59E0B', '#10B981', + '#6366F1', '#F97316', '#14B8A6', '#EF4444', '#8B5CF6' + ] + categories = df.columns graph_data = [] - for acct, data in df.iterrows(): - bar = go.Bar(name=acct, x=categories, y=data) + + for idx, (acct, data) in enumerate(df.iterrows()): + bar = go.Bar( + name=acct, + x=categories, + y=data, + marker=dict( + color=account_colors[idx % len(account_colors)], + line=dict(color='rgba(255, 255, 255, 0.6)', width=1), + opacity=0.9 + ), + text=data, + textposition='outside', + textfont=dict(size=10, color='#1F2937', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + hovertemplate='' + str(acct) + '
%{x}
Failed Resources: %{y}' + ) graph_data.append(bar) - fig = go.Figure( - data=graph_data - ) + fig = go.Figure(data=graph_data) + fig.update_layout( - title='Compliance Severities by Service', + title=dict( + text='Compliance Findings by Service Category', + font=dict(size=18, color='#111827', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', weight=600), + x=0.5, + xanchor='center' + ), yaxis=dict( - title='Failed resources' + title='Failed Resources', + titlefont=dict(size=14, color='#4B5563', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + tickfont=dict(size=12, color='#6B7280'), + gridcolor='#E5E7EB', + gridwidth=1, + showgrid=True, + zeroline=False ), - barmode='group' + xaxis=dict( + tickangle=-45, + tickfont=dict(size=11, color='#6B7280', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + showgrid=False + ), + barmode='group', + plot_bgcolor='rgba(249, 250, 251, 0.5)', + paper_bgcolor='white', + margin=dict(l=60, r=40, t=100, b=120), + font=dict(family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + legend=dict( + orientation='v', + yanchor='top', + y=1, + xanchor='left', + x=1.02, + bgcolor='rgba(255, 255, 255, 0.9)', + bordercolor='#E5E7EB', + borderwidth=1, + font=dict(size=11, color='#374151') + ), + hoverlabel=dict( + bgcolor='white', + font_size=13, + font_family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + bordercolor='#E5E7EB' + ), + uniformtext=dict(mode='hide', minsize=8) ) + img_bytes = fig.to_image(format=format, width=width, height=height) return img_bytes \ No newline at end of file diff --git a/modules/container_vulnerabilities.py b/modules/container_vulnerabilities.py index 5b8e0d9..3d983e1 100644 --- a/modules/container_vulnerabilities.py +++ b/modules/container_vulnerabilities.py @@ -7,7 +7,57 @@ class ContainerVulnerabilities: def __init__(self, raw_data): - self.data = raw_data + # Filter for CVSS score 10.0 only + self.data = self._filter_by_cvss_score(raw_data, target_score=10.0) + + def _filter_by_cvss_score(self, raw_data, target_score=10.0): + """Filter vulnerabilities by CVSS score. + + Args: + raw_data: List of vulnerability records + target_score: Target CVSS score (default 10.0) + + Returns: + Filtered list of vulnerabilities with CVSS score >= target_score + """ + filtered_data = [] + for vuln in raw_data: + # Try to extract CVSS score from various possible locations + cvss_score = None + + # Check cveProps.metadata.NVD.CVSSv3.Score + if 'cveProps' in vuln and vuln['cveProps']: + cve_props = vuln['cveProps'] + if 'metadata' in cve_props and cve_props['metadata']: + metadata = cve_props['metadata'] + if 'NVD' in metadata and metadata['NVD']: + nvd = metadata['NVD'] + if 'CVSSv3' in nvd and nvd['CVSSv3']: + cvss_v3 = nvd['CVSSv3'] + if 'Score' in cvss_v3: + cvss_score = float(cvss_v3['Score']) + + # Also check for cvssV3Score directly in cveProps + if cvss_score is None and 'cvssV3Score' in cve_props: + cvss_score = float(cve_props['cvssV3Score']) + + # Check for cvss_v3_score + if cvss_score is None and 'cvss_v3_score' in cve_props: + cvss_score = float(cve_props['cvss_v3_score']) + + # Check top-level fields + if cvss_score is None and 'cvssScore' in vuln: + cvss_score = float(vuln['cvssScore']) + + if cvss_score is None and 'cvss_score' in vuln: + cvss_score = float(vuln['cvss_score']) + + # If we found a CVSS score and it matches our target, include it + if cvss_score is not None and cvss_score >= target_score: + filtered_data.append(vuln) + + logger.info(f'Filtered container vulnerabilities: {len(raw_data)} -> {len(filtered_data)} (CVSS >= {target_score})') + return filtered_data def total_evaluated(self): df = pd.DataFrame(self.data) @@ -167,14 +217,58 @@ def top_packages_bar(self, width=600, height=350, format='svg', limit: int = 10) df = self.summary_by_package().head(limit) import plotly.graph_objects as go - # Use textposition='auto' for direct text - fig = go.Figure(data=[go.Bar(x=df['Package Info'], y=df['Count'])]) + # Modern gradient color scheme + gradient_colors = ['#6366F1', '#8B5CF6', '#A855F7', '#C026D3', '#D946EF', + '#E879F9', '#F0ABFC', '#F5D0FE', '#FAE8FF', '#FDF4FF'][:len(df)] + + # Create modern bar chart + fig = go.Figure(data=[go.Bar( + x=df['Package Info'], + y=df['Count'], + marker=dict( + color=gradient_colors, + line=dict(color='rgba(255, 255, 255, 0.8)', width=1.5), + opacity=0.9 + ), + text=df['Count'], + textposition='outside', + textfont=dict(size=11, color='#1F2937', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + hovertemplate='%{x}
Affected Images: %{y}' + )]) + fig.update_layout( - title='High Priority Packages to Patch (by CVE Count)', + title=dict( + text='High Priority Packages to Patch', + font=dict(size=18, color='#111827', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', weight=600), + x=0.5, + xanchor='center' + ), yaxis=dict( - title='Number of Affected Images' + title='Number of Affected Images', + titlefont=dict(size=14, color='#4B5563', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + tickfont=dict(size=12, color='#6B7280'), + gridcolor='#E5E7EB', + gridwidth=1, + showgrid=True, + zeroline=False ), - xaxis_tickangle=45 + xaxis=dict( + tickangle=-45, + tickfont=dict(size=11, color='#6B7280', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + showgrid=False + ), + plot_bgcolor='rgba(249, 250, 251, 0.5)', + paper_bgcolor='white', + margin=dict(l=60, r=40, t=100, b=120), + font=dict(family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + hoverlabel=dict( + bgcolor='white', + font_size=13, + font_family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + bordercolor='#E5E7EB' + ), + uniformtext=dict(mode='hide', minsize=8) ) + img_bytes = fig.to_image(format=format, width=width, height=height) return img_bytes \ No newline at end of file diff --git a/modules/host_vulnerabilities.py b/modules/host_vulnerabilities.py index 03079cd..d288124 100644 --- a/modules/host_vulnerabilities.py +++ b/modules/host_vulnerabilities.py @@ -7,7 +7,72 @@ class HostVulnerabilities: def __init__(self, raw_data): - self.data = raw_data + # Filter Critical to CVSS score 10.0 only, keep all High/Medium/Low + self.data = self._filter_by_severity_and_cvss(raw_data) + + def _filter_by_severity_and_cvss(self, raw_data): + """Filter vulnerabilities: Critical must have CVSS 10.0, keep all High/Medium/Low. + + Args: + raw_data: List of vulnerability records + + Returns: + Filtered list of vulnerabilities + """ + filtered_data = [] + critical_before = 0 + critical_after = 0 + + for vuln in raw_data: + severity = vuln.get('severity', '') + + # Keep all High, Medium, Low vulnerabilities + if severity in ['High', 'Medium', 'Low']: + filtered_data.append(vuln) + continue + + # For Critical, only keep if CVSS score is 10.0 + if severity == 'Critical': + critical_before += 1 + cvss_score = None + + # Try to extract CVSS score from various possible locations + if 'cveProps' in vuln and vuln['cveProps']: + cve_props = vuln['cveProps'] + if 'metadata' in cve_props and cve_props['metadata']: + metadata = cve_props['metadata'] + if 'NVD' in metadata and metadata['NVD']: + nvd = metadata['NVD'] + if 'CVSSv3' in nvd and nvd['CVSSv3']: + cvss_v3 = nvd['CVSSv3'] + if 'Score' in cvss_v3: + cvss_score = float(cvss_v3['Score']) + + # Also check for cvssV3Score directly in cveProps + if cvss_score is None and 'cvssV3Score' in cve_props: + cvss_score = float(cve_props['cvssV3Score']) + + # Check for cvss_v3_score + if cvss_score is None and 'cvss_v3_score' in cve_props: + cvss_score = float(cve_props['cvss_v3_score']) + + # Check top-level fields + if cvss_score is None and 'cvssScore' in vuln: + cvss_score = float(vuln['cvssScore']) + + if cvss_score is None and 'cvss_score' in vuln: + cvss_score = float(vuln['cvss_score']) + + # Only include Critical if CVSS score is 10.0 + if cvss_score is not None and cvss_score >= 10.0: + filtered_data.append(vuln) + critical_after += 1 + + logger.info(f'Filtered vulnerabilities: {len(raw_data)} total') + logger.info(f' Critical: {critical_before} -> {critical_after} (CVSS >= 10.0)') + logger.info(f' High/Medium/Low: kept all') + logger.info(f' Final count: {len(filtered_data)}') + return filtered_data def total_evaluated(self): df = pd.DataFrame(self.data) @@ -68,8 +133,7 @@ def fixable_vulns(self, severities=("Critical", "High"), limit=False): # df = df.groupby(['evalCtx.hostname', 'featureKey.name', 'featureKey.version_installed', 'severity', 'vulnId'], # as_index=False).agg({'fixInfo.fixed_version': ', '.join}) df = df.groupby(['evalCtx.hostname', 'severity', 'vulnId', 'featureKey.name', 'featureKey.version_installed'], - as_index=False).agg(pd.unique).applymap(lambda x: x[0] if len(x) == 1 else x) - print(df) + as_index=False).agg(lambda x: ', '.join(x.unique()) if x.dtype == 'object' else x.iloc[0]) df = df.groupby(['evalCtx.hostname', 'severity', 'featureKey.name', 'fixInfo.fixed_version','featureKey.version_installed' ], as_index=False).agg({'vulnId': ', '.join}) # rename columns df.rename(columns={'evalCtx.hostname': 'Hostname', @@ -115,24 +179,119 @@ def summary(self, severities=("Critical", "High", "Medium", "Low")): return df + def all_cves_detail(self, severities=("High", "Medium", "Low"), limit=100): + """Get detailed CVE information for specified severities. + + Args: + severities: Tuple of severity levels to include + limit: Maximum number of CVEs to return + + Returns: + DataFrame with CVE details + """ + df = pd.json_normalize(self.data, + meta=[['evalCtx', 'hostname'], + ['featureKey', 'name'], + 'vulnId', + 'severity', + ['fixInfo', 'fix_available'], + ['fixInfo', 'fixed_version'], + ['featureKey', 'version_installed']]) + + if 'severity' not in df: + df['severity'] = False + + # Filter by severity + df = df[df['severity'].isin(severities)] + + if df.empty: + return df + + # Clean up column names + df.rename(columns={ + 'evalCtx.hostname': 'Hostname', + 'vulnId': 'CVE', + 'severity': 'Severity', + 'featureKey.name': 'Package Name', + 'featureKey.version_installed': 'Installed Version', + 'fixInfo.fix_available': 'Fix Available', + 'fixInfo.fixed_version': 'Fixed Version(s)' + }, inplace=True) + + # Sort by severity then CVE + df['Severity'] = pd.Categorical(df['Severity'], ["Critical", "High", "Medium", "Low", "Info"]) + df = df.sort_values(by=['Severity', 'CVE']) + + # Select and reorder columns + columns_to_keep = ['Severity', 'CVE', 'Hostname', 'Package Name', 'Installed Version'] + if 'Fix Available' in df.columns and 'Fixed Version(s)' in df.columns: + columns_to_keep.extend(['Fix Available', 'Fixed Version(s)']) + + df = df[columns_to_keep] + + if limit: + df = df.head(limit) + + return df + def host_vulns_by_severity_bar(self, severities=["Critical", "High", "Medium", "Low"], width=600, height=350, format='svg'): df = self.summary(severities=severities) + # Modern gradient-inspired color palette with better contrast colors = [ - 'crimson', - 'darkorange', - 'gold', - 'lightskyblue' + '#DC2626', # Modern red for Critical + '#F97316', # Vibrant orange for High + '#FBBF24', # Warm yellow for Medium + '#3B82F6' # Clean blue for Low ] - # Use textposition='auto' for direct text - fig = go.Figure(data=[go.Bar(x=df['Severity'], y=df['Total CVEs'], marker_color=colors)]) + # Create bar chart with modern styling + fig = go.Figure(data=[go.Bar( + x=df['Severity'], + y=df['Total CVEs'], + marker=dict( + color=colors, + line=dict(color='rgba(255, 255, 255, 0.8)', width=1.5), + opacity=0.9 + ), + text=df['Total CVEs'], + textposition='outside', + textfont=dict(size=12, color='#1F2937', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + hovertemplate='%{x}
CVE Count: %{y}' + )]) fig.update_layout( - title='Host Severities by CVE', + title=dict( + text='Host Vulnerabilities by Severity', + font=dict(size=18, color='#111827', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', weight=600), + x=0.5, + xanchor='center' + ), yaxis=dict( - title='Number of CVEs' - ) + title='Number of CVEs', + titlefont=dict(size=14, color='#4B5563', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + tickfont=dict(size=12, color='#6B7280'), + gridcolor='#E5E7EB', + gridwidth=1, + showgrid=True, + zeroline=False + ), + xaxis=dict( + titlefont=dict(size=14, color='#4B5563', family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + tickfont=dict(size=12, color='#6B7280'), + showgrid=False + ), + plot_bgcolor='rgba(249, 250, 251, 0.5)', + paper_bgcolor='white', + margin=dict(l=60, r=40, t=100, b=60), + font=dict(family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'), + hoverlabel=dict( + bgcolor='white', + font_size=13, + font_family='Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', + bordercolor='#E5E7EB' + ), + uniformtext=dict(mode='hide', minsize=8) ) img_bytes = fig.to_image(format=format, width=width, height=height) diff --git a/modules/identity_entitlements.py b/modules/identity_entitlements.py new file mode 100644 index 0000000..8da7f23 --- /dev/null +++ b/modules/identity_entitlements.py @@ -0,0 +1,183 @@ +import pandas as pd +from logzero import logger +from datetime import * + + +class IdentityEntitlements: + + def __init__(self, raw_data): + self.data = raw_data['data'] + + def _process_data(self): + """ + Process raw LQL data into a pandas DataFrame with calculated fields + """ + if not self.data: + return pd.DataFrame() + + processed_data = [] + for item in self.data: + # Extract entitlement counts - use correct field names from Lacework + entitlement_counts = item.get('ENTITLEMENT_COUNTS', {}) + used_count = entitlement_counts.get('entitlements_used_count', 0) + unused_count = entitlement_counts.get('entitlements_unused_count', 0) + total_count = entitlement_counts.get('entitlements_total_count', used_count + unused_count) + + # Calculate unused percentage + unused_percentage = 0 + if total_count > 0: + unused_percentage = round((unused_count / total_count) * 100, 1) + + # Extract risk metrics + metrics = item.get('METRICS', {}) + risks = metrics.get('risks', []) if metrics else [] + risk_severity = metrics.get('risk_severity', 'UNKNOWN') if metrics else 'UNKNOWN' + + # Check if this is a root/admin identity + has_full_admin = 'ALLOWS_FULL_ADMIN' in risks + + # Check MFA status from risks + # PASSWORD_LOGIN_NO_MFA indicates user has password login enabled without MFA + has_mfa_disabled = 'PASSWORD_LOGIN_NO_MFA' in risks + mfa_status = 'Disabled' if has_mfa_disabled else 'Enabled' + + processed_data.append({ + 'RECORD_CREATED_TIME': item.get('RECORD_CREATED_TIME', ''), + 'PRINCIPAL_ID': item.get('PRINCIPAL_ID', ''), + 'NAME': item.get('NAME', ''), + 'PROVIDER_TYPE': item.get('PROVIDER_TYPE', ''), + 'DOMAIN_ID': item.get('DOMAIN_ID', ''), + 'LAST_USED_TIME': item.get('LAST_USED_TIME', ''), + 'CREATED_TIME': item.get('CREATED_TIME', ''), + 'used_count': used_count, + 'unused_count': unused_count, + 'total_count': total_count, + 'unused_percentage': unused_percentage, + 'risks': risks, + 'risk_severity': risk_severity, + 'has_full_admin': has_full_admin, + 'mfa_status': mfa_status, + 'has_mfa_disabled': has_mfa_disabled + }) + + return pd.DataFrame(processed_data) + + def get_high_privilege_identities(self, threshold=70): + """ + Get identities with unused entitlements >= threshold COUNT (not percentage) + + Args: + threshold: Minimum unused entitlement COUNT (default 70) + + Returns: + pandas DataFrame of identities meeting criteria + """ + df = self._process_data() + if df.empty: + return df + + # Filter by unused COUNT threshold (matching original ciem.sh behavior) + high_priv = df[df['unused_count'] >= threshold] + + # Sort by unused count descending + high_priv = high_priv.sort_values(by='unused_count', ascending=False) + + return high_priv + + def get_root_identities(self): + """ + Get identities with ALLOWS_FULL_ADMIN risk + + Returns: + pandas DataFrame of root/admin identities + """ + df = self._process_data() + if df.empty: + return df + + # Filter by full admin access + root = df[df['has_full_admin'] == True] + + # Sort by risk severity and unused percentage + root = root.sort_values(by=['risk_severity', 'unused_percentage'], ascending=[False, False]) + + return root + + def get_critical_identities(self, threshold=70): + """ + Get identities that are BOTH root/admin AND have high unused entitlements + This represents the highest risk identities + + Args: + threshold: Minimum unused entitlement COUNT (default 70) + + Returns: + pandas DataFrame of critical risk identities + """ + df = self._process_data() + if df.empty: + return df + + # Filter by both criteria: full admin AND high unused COUNT + critical = df[(df['has_full_admin'] == True) & (df['unused_count'] >= threshold)] + + # Sort by unused count descending + critical = critical.sort_values(by='unused_count', ascending=False) + + return critical + + def get_summary_counts(self, threshold=70): + """ + Get summary statistics for template + + Args: + threshold: Minimum unused entitlement COUNT (default 70) + + Returns: + Dictionary with count statistics + """ + df = self._process_data() + if df.empty: + return { + 'total_count': 0, + 'high_privilege_count': 0, + 'root_count': 0, + 'critical_count': 0, + 'threshold': threshold + } + + return { + 'total_count': len(df), + 'high_privilege_count': len(df[df['unused_count'] >= threshold]), + 'root_count': len(df[df['has_full_admin'] == True]), + 'critical_count': len(df[(df['has_full_admin'] == True) & (df['unused_count'] >= threshold)]), + 'threshold': threshold + } + + def count_identities(self): + """ + Get total count of identities + + Returns: + Integer count + """ + return len(self.data) + + def get_all_identities(self, limit=25): + """ + Get all identities sorted by unused percentage (highest first) + + Args: + limit: Maximum number of identities to return (default 25) + + Returns: + pandas DataFrame of identities + """ + df = self._process_data() + if df.empty: + return df + + # Sort by unused percentage descending, then by has_full_admin + df = df.sort_values(by=['has_full_admin', 'unused_percentage'], ascending=[False, False]) + + return df.head(limit) diff --git a/modules/lacework_interface.py b/modules/lacework_interface.py index d1020c4..ca32ed4 100644 --- a/modules/lacework_interface.py +++ b/modules/lacework_interface.py @@ -8,6 +8,7 @@ from modules.alerts import Alerts from modules.compliance import Compliance from modules.secrets import Secrets +from modules.identity_entitlements import IdentityEntitlements from modules.utils import cache_results @@ -138,7 +139,69 @@ def get_secrets(self, start_time, end_time): return Secrets(secrets) @cache_results - def get_host_vulns(self, start_time, end_time, severities=("Critical", "High", "Medium")): + def get_identity_entitlements(self, start_time, end_time): + """ + Query LW_CE_IDENTITIES datasource for identity and entitlement data + Returns ALL identities from all cloud providers in one query + + Args: + start_time: ISO format timestamp + end_time: ISO format timestamp + + Returns: + IdentityEntitlements object + """ + logger.debug(f'Getting identity entitlements from {start_time} to {end_time}:') + + lql_query = """ + { + source { + LW_CE_IDENTITIES + } + return distinct { + RECORD_CREATED_TIME, + PRINCIPAL_ID, + PROVIDER_TYPE, + DOMAIN_ID, + NAME, + LAST_USED_TIME, + CREATED_TIME, + METRICS, + TAGS, + ACCESS_KEYS, + ACCESS_KEYS_LIST, + ENTITLEMENT_COUNTS + } + } + """ + + lql_query_args = { + "StartTimeRange": start_time, + "EndTimeRange": end_time + } + + try: + logger.debug(f"Executing LQL query for all identities...") + identity_data = self.lacework.queries.execute( + query_text=lql_query, + arguments=lql_query_args + ) + except Exception as e: + logger.error(f"Failed to retrieve identity entitlements: {str(e)}") + logger.debug(f"Query was: {lql_query}") + logger.debug(f"Arguments were: {lql_query_args}") + raise e + + identity_count = len(identity_data.get("data", [])) + logger.info(f'{identity_count} total identities returned.') + + if identity_count == 0: + logger.warning(f"No identity data found. CIEM may not be enabled for this account.") + + return IdentityEntitlements(identity_data) + + @cache_results + def get_host_vulns(self, start_time, end_time, severities=("Critical", "High", "Medium", "Low")): results = [] for severity in severities: filters = { @@ -165,18 +228,20 @@ def get_host_vulns(self, start_time, end_time, severities=("Critical", "High", " i = 1 for page in host_vulns: - logger.info('Saving page ' + str(i)) + logger.info('Saving page ' + str(i) + f' with {len(page.get("data", []))} records') i = i + 1 results.extend(page['data']) + logger.info(f'Total records for severity {severity}: {i-1} pages') if i > 100: logger.warning( "Lacework API returned maximum pages of host vuln results (100 pages). Processed dataset is likely incomplete.") + logger.info(f'Total host vulnerability records retrieved: {len(results)}') host_vulns = HostVulnerabilities(results) return host_vulns @cache_results - def get_container_vulns(self, start_time, end_time, severities=("Critical", "High", "Medium")): + def get_container_vulns(self, start_time, end_time, severities=("Critical",)): results = [] for severity in severities: filters = { diff --git a/modules/process_args.py b/modules/process_args.py index 275eb65..9b609bc 100644 --- a/modules/process_args.py +++ b/modules/process_args.py @@ -57,7 +57,7 @@ def get_arguments(): parser.add_argument("--cache-data", help="Create/use locally cached copies of Lacework data. This is mainly used for dev testing.", action='store_true') parser.add_argument("--vulns-start-time", type=str, help="The number of days and hours in the past relative to NOW to start the vulnerability report. In the format ", - default="0:25") + default="7:0") parser.add_argument("--vulns-end-time", type=str, help="The number of days and hours in the past relative to NOW to end the vulnerability report. In the format (use 0:0 for now)", default="0:0") @@ -75,6 +75,7 @@ def get_arguments(): parser.add_argument("--report-format", help="Specify output format, HTML or PDF. Default is HTML", default="HTML") parser.add_argument("--gui", help="Run this tool in GUI mode, which provides additional customization options.", action='store_true') parser.add_argument("--logo", type=str, help="Specify a custom logo (PNG file) to add to the report.") + parser.add_argument("--ciem-threshold", type=int, default=70, help="Unused entitlement count threshold for CIEM analysis (default: 70)") parser.add_argument("--list-reports", help="List the available reports to generate. Default is 'CSA'", action='store_true') return parser.parse_args() diff --git a/modules/reportgen.py b/modules/reportgen.py index ade9db8..9707a10 100644 --- a/modules/reportgen.py +++ b/modules/reportgen.py @@ -86,6 +86,7 @@ def gather_host_vulnerability_data(self, begin_time: str, end_time: str, host_li summary_bar_graphic = host_vulnerabilities.host_vulns_by_severity_bar(width=1200 * self.graph_scale, height=350 * self.graph_scale) summary_bar_graphic_encoded = self.bytes_to_image_tag(summary_bar_graphic, "svg+xml", align='middle') fixable_vulns = host_vulnerabilities.fixable_vulns(severities=["Critical"]) + all_cves_detail = host_vulnerabilities.all_cves_detail(severities=("High", "Medium", "Low"), limit=100) return { 'hosts_scanned_count': total_evaluated, 'host_vulns_summary': summary, @@ -93,7 +94,8 @@ def gather_host_vulnerability_data(self, begin_time: str, end_time: str, host_li 'host_vulns_summary_by_host': summary_by_host, 'critical_vuln_count': critical_vulnerability_count, 'host_vulns_summary_by_host_limit': host_limit, - 'fixable_vulns': fixable_vulns + 'fixable_vulns': fixable_vulns, + 'all_cves_detail': all_cves_detail } def gather_container_vulnerability_data(self, begin_time: str, end_time: str, container_limit: int = 25): @@ -159,11 +161,11 @@ def gather_compliance_data(self, cloud_provider='AWS', report_type='CIS'): findings_summary_by_service_bar_graph_encoded = self.bytes_to_image_tag(findings_summary_by_service_bar_graph, 'svg+xml', align='middle') critical_details = compliance_reports.critical_compliance_details() summary_by_account = compliance_reports.get_summary_by_account() - summary_count = summary_by_account.shape[0] - if 'Critical' in summary_by_account.columns: - critical_finding_count = summary_by_account['Critical'].sum() - else: - critical_finding_count = 0 + + # Count unique compliance findings (not resources) across all accounts + # If the same control fails in multiple accounts, it's only counted once + summary_count = compliance_reports.get_unique_finding_count(severities=["Critical", "High"]) + critical_finding_count = compliance_reports.get_unique_critical_finding_count() return { 'cloud_accounts_count': compliance_reports.get_total_accounts_evaluated(), @@ -217,6 +219,118 @@ def gather_alert_data(self, begin_time: str, end_time: str): else: return None + def gather_identity_entitlement_data(self, begin_time: str, end_time: str, threshold: int = 70): + """ + Gather CIEM data for AWS, Azure, GCP identities + + Args: + begin_time: ISO timestamp + end_time: ISO timestamp + threshold: Unused entitlement threshold (default 70) + + Returns: + Dictionary with CIEM data for all providers or False on error + """ + print('Gathering identity and entitlement data (CIEM)...') + + # Get ALL identities in one query + try: + self.lacework_interface.use_cache = self.use_cache + all_identity_entitlements = self.lacework_interface.get_identity_entitlements( + begin_time, + end_time + ) + except Exception as e: + logger.error(f'Failed to retrieve CIEM data: {str(e)}') + logger.error(traceback.format_exc()) + return False + + if not all_identity_entitlements.data: + logger.error("No CIEM data returned. CIEM may not be enabled for this account.") + logger.info("Skipping CIEM section in report.") + return False + + print(f'Total identities retrieved: {all_identity_entitlements.count_identities()}') + + # Now filter by provider and process each separately + ciem_data = {} + from modules.identity_entitlements import IdentityEntitlements + + # Define per-provider thresholds + provider_thresholds = { + 'AWS': threshold, + 'AZURE': threshold, + 'GCP': threshold + } + + for provider in ['AWS', 'AZURE', 'GCP']: + # Filter identities by provider type + provider_identities = [identity for identity in all_identity_entitlements.data + if identity.get('PROVIDER_TYPE') == provider] + + if not provider_identities: + logger.warning(f"No CIEM data returned for {provider}") + ciem_data[provider] = None + continue + + # Create a new IdentityEntitlements object for this provider + identity_entitlements = IdentityEntitlements({'data': provider_identities}) + + # Use provider-specific threshold + provider_threshold = provider_thresholds[provider] + + # Get critical identities (root + high privileges) + critical_identities = identity_entitlements.get_critical_identities(provider_threshold) + high_privilege_identities = identity_entitlements.get_high_privilege_identities(provider_threshold) + root_identities = identity_entitlements.get_root_identities() + + print(f'{provider}: Total identities analyzed: {identity_entitlements.count_identities()}') + print(f'{provider}: High privilege identities (≥{provider_threshold} unused entitlements): {len(high_privilege_identities)}') + print(f'{provider}: Root/admin identities: {len(root_identities)}') + print(f'{provider}: Critical (root + ≥{provider_threshold} unused): {len(critical_identities)}') + + # Get all identities for display (top 25) + all_identities = identity_entitlements.get_all_identities(limit=25) + + # For Azure, also get count of identities with ≥80 for executive summary + high_risk_count_80 = 0 + if provider == 'AZURE': + high_risk_80 = identity_entitlements.get_high_privilege_identities(80) + high_risk_count_80 = len(high_risk_80) + print(f'{provider}: High risk identities (≥80 unused entitlements): {high_risk_count_80}') + + # Process data for this provider + ciem_data[provider] = { + 'high_privilege_count': len(high_privilege_identities), + 'root_count': len(root_identities), + 'critical_count': len(critical_identities), + 'critical_identities': critical_identities, + 'high_privilege_identities': high_privilege_identities, + 'root_identities': root_identities, + 'all_identities': all_identities, + 'threshold': provider_threshold, + 'total_identities': identity_entitlements.count_identities() + } + + # Add Azure-specific high risk count for executive summary + if provider == 'AZURE': + ciem_data[provider]['high_risk_count_80'] = high_risk_count_80 + + # Check if we got any data from at least one provider + providers_with_data = [k for k, v in ciem_data.items() if v is not None] + + if not providers_with_data: + logger.error("No CIEM data retrieved for any provider. CIEM may not be enabled for this account.") + logger.info("Skipping CIEM section in report.") + return False + + if len(providers_with_data) < 3: + logger.info(f"CIEM data available for: {', '.join(providers_with_data)}") + missing_providers = [k for k, v in ciem_data.items() if v is None] + logger.warning(f"No CIEM data for: {', '.join(missing_providers)} (may not be configured)") + + return ciem_data + def gather_data(self, vulns_start_time: LaceworkTime, vulns_end_time: LaceworkTime, diff --git a/modules/reports/reportgen_csa.py b/modules/reports/reportgen_csa.py index 7af9a1a..5b8b36d 100644 --- a/modules/reports/reportgen_csa.py +++ b/modules/reports/reportgen_csa.py @@ -59,7 +59,7 @@ def render(self, customer, author, custom_logo=None, pagesize="a3"): def generate(self, customer: str, author: str, - vulns_start_time: LaceworkTime = LaceworkTime('0:25'), + vulns_start_time: LaceworkTime = LaceworkTime('7:0'), vulns_end_time: LaceworkTime = LaceworkTime('0:0'), alerts_start_time: LaceworkTime = LaceworkTime('7:0'), alerts_end_time: LaceworkTime = LaceworkTime('0:0'), diff --git a/modules/reports/reportgen_csa_detailed.py b/modules/reports/reportgen_csa_detailed.py index bd225b9..c13f396 100644 --- a/modules/reports/reportgen_csa_detailed.py +++ b/modules/reports/reportgen_csa_detailed.py @@ -22,12 +22,18 @@ def __init__(self, basedir, use_cache=False, api_key_file=None, graph_scale=1): self.template = self.get_jinja2_template('csa_detailed_report.jinja2') self.company_logo_html = self.file_to_image_tag('assets/Fortinet_logo.png', 'png') self.polygraph_graphic_html = self.file_to_image_tag('assets/FortiCNAPP-Unified-Approach.png', 'png') + self.sec_outcomes_html = self.file_to_image_tag('assets/sec_outcomes.png', 'png') + self.cloud_status_quo_html = self.file_to_image_tag('assets/CloudStatusQuo.png', 'png') + self.fortinet_sec_fabric_html = self.file_to_image_tag('assets/FortinetSecFabric.png', 'png') + self.forticnapp_platform_html = self.file_to_image_tag('assets/FortiCNAPP.png', 'png') + self.state_of_cloud_html = self.file_to_image_tag('assets/StateofCloud_SecReport.png', 'png') def gather_data(self, vulns_start_time: LaceworkTime, vulns_end_time: LaceworkTime, alerts_start_time: LaceworkTime, - alerts_end_time: LaceworkTime): + alerts_end_time: LaceworkTime, + ciem_threshold: int = 70): self.aws_compliance_data=self.gather_compliance_data(cloud_provider='AWS') self.azure_compliance_data=self.gather_compliance_data(cloud_provider='AZURE') @@ -36,6 +42,7 @@ def gather_data(self, self.container_vulns_data=self.gather_container_vulnerability_data(vulns_start_time.generate_time_string(), vulns_end_time.generate_time_string()) self.alerts_data=self.gather_alert_data(alerts_start_time.generate_time_string(), alerts_end_time.generate_time_string()) self.secrets_data=self.gather_secrets(alerts_start_time.generate_time_string(), alerts_end_time.generate_time_string()) + self.ciem_data=self.gather_identity_entitlement_data(alerts_start_time.generate_time_string(), alerts_end_time.generate_time_string(), threshold=ciem_threshold) def render(self, customer, author, pagesize="a3", custom_logo=None, pdf=False): if custom_logo and os.path.isfile(custom_logo): @@ -50,6 +57,11 @@ def render(self, customer, author, pagesize="a3", custom_logo=None, pdf=False): company_logo_html=self.company_logo_html, custom_logo_html=self.custom_logo_html, polygraph_graphic_html=self.polygraph_graphic_html, + sec_outcomes_html=self.sec_outcomes_html, + cloud_status_quo_html=self.cloud_status_quo_html, + fortinet_sec_fabric_html=self.fortinet_sec_fabric_html, + forticnapp_platform_html=self.forticnapp_platform_html, + state_of_cloud_html=self.state_of_cloud_html, aws_compliance_data=self.aws_compliance_data, azure_compliance_data=self.azure_compliance_data, gcp_compliance_data=self.gcp_compliance_data, @@ -57,6 +69,7 @@ def render(self, customer, author, pagesize="a3", custom_logo=None, pdf=False): container_vulns_data=self.container_vulns_data, alerts_data=self.alerts_data, secrets_data=self.secrets_data, + ciem_data=self.ciem_data, recommendations=self.recommendations, pagesize=pagesize, pdf=pdf @@ -65,17 +78,19 @@ def render(self, customer, author, pagesize="a3", custom_logo=None, pdf=False): def generate(self, customer: str, author: str, - vulns_start_time: LaceworkTime = LaceworkTime('0:25'), + vulns_start_time: LaceworkTime = LaceworkTime('7:0'), vulns_end_time: LaceworkTime = LaceworkTime('0:0'), alerts_start_time: LaceworkTime = LaceworkTime('7:0'), alerts_end_time: LaceworkTime = LaceworkTime('0:0'), + ciem_threshold: int = 70, custom_logo=None, pagesize="a3", pdf=False): self.gather_data(vulns_start_time, vulns_end_time, alerts_start_time, - alerts_end_time) + alerts_end_time, + ciem_threshold=ciem_threshold) return self.render(customer, author, custom_logo=custom_logo, pagesize=pagesize, pdf=pdf) diff --git a/recommendations.htlm b/recommendations.htlm new file mode 100644 index 0000000..fddaa2a --- /dev/null +++ b/recommendations.htlm @@ -0,0 +1,54 @@ +

Recommendations

+

+Based on the findings of this assessment, Fortinet recommends the following action plan and next steps: +

+ +

Ottawa Community Housing Azure Security Remediation Playbook

+

Goal: Fix all Critical vulnerabilities and achieve CIS compliance using FortiCNAPP Rapid Assessment Report.

+ +

CIS Top 3 Critical Items

+ + +

Critical Vulnerabilities samples (CVSS 9.0+):

+ + +

Daily Tasks:

+
    +
  1. Using FortiCNAPP Report Assessment address High vulnerabilities (CVSS >= 9) during scheduled maintenance windows
  2. +
  3. Validation: Re-scan environment for Critical/High findings
  4. +
+ +

High-Priority Misconfigurations:

+ + +

Recommended Fortinet Solutions

+

Minimum Fortinet Protection Cloud Stack:

+ +
Cloud-Native Protection
+ + +

Next Steps

+
    +
  1. Engage with your Fortinet account team and partner to review services offerings to prioritize and remediate the findings
  2. +
  3. Complete a recurring Cloud Security Assessment once a wider FortiCNAPP deployment has been completed to baseline and trend improvements to your cloud security posture
  4. +
diff --git a/report.md b/report.md new file mode 100644 index 0000000..2976439 --- /dev/null +++ b/report.md @@ -0,0 +1,32 @@ + + # Default (70% unused entitlement threshold) + python lw_report_gen.py \ + --report CSA_Detailed \ + --format HTML \ + --customer "Customer Name" \ + --author "Your Name" + + # Custom threshold (50% - shows more identities) + python lw_report_gen.py \ + --report CSA_Detailed \ + --format PDF \ + --customer "Customer Name" \ + --ciem-threshold 50 \ + --alerts-start-time 30:0 + + # With caching for testing + python lw_report_gen.py \ + --report CSA_Detailed \ + --cache-data \ + --customer "Test" \ + --ciem-threshold 90 + + Key Features + + - Dynamic Time Ranges: Uses the --alerts-start-time parameter (7/30/90 days) + - Unified Report: CIEM data appears alongside compliance, vulnerabilities, and alerts + - Configurable Threshold: Adjust the unused entitlement percentage filter + - Multi-Cloud: Analyzes AWS, Azure, and GCP identities + - Both Formats: Works with HTML and PDF output + + diff --git a/templates/csa_detailed_report.jinja2 b/templates/csa_detailed_report.jinja2 index 620d012..b45ff31 100644 --- a/templates/csa_detailed_report.jinja2 +++ b/templates/csa_detailed_report.jinja2 @@ -3,376 +3,691 @@ - CSA Report + FortiCNAPP Rapid Cloud Assessment - {{customer}} - +
{% if company_logo_html %} {{ company_logo_html | safe }} @@ -380,362 +695,953 @@ {% if custom_logo_html %} {{ custom_logo_html | safe}} {% endif %} -
-
-
-

Assessment Report

-

Report created for {{customer}}

-

{{date}}

-
- Generated by {{author}} -
- {% set summary_data = [] %} - {% if aws_compliance_data %} - {% set _ = summary_data.append('AWS Accounts Analyzed: ' ~ aws_compliance_data.cloud_accounts_count) %} - {% endif %} - {% if azure_compliance_data %} - {% set _ = summary_data.append('Azure Subscriptions Analyzed: ' ~ azure_compliance_data.cloud_accounts_count) %} - {% endif %} - {% if gcp_compliance_data %} - {% set _ = summary_data.append('GCP Projects Analyzed: ' ~ gcp_compliance_data.cloud_accounts_count) %} - {% endif %} - {% if host_vulns_data %} - {% set _ = summary_data.append('Hosts Scanned: ' ~ host_vulns_data.hosts_scanned_count) %} - {% endif %} - {% if container_vulns_data %} - {% set _ = summary_data.append('Containers Scanned: ' ~ container_vulns_data.containers_scanned_count) %} - {% endif %} - {{ summary_data | join(', ') }} -
- {% if pdf %} -
- {% if polygraph_graphic_html %} - {{ polygraph_graphic_html | safe }} - {% endif %} -
- {% else %} -
- {% if polygraph_graphic_html %} - {{ polygraph_graphic_html | safe }} - {% endif %} + +
+

FortiCNAPP Rapid Cloud Assessment

+
Comprehensive Security Posture Analysis for {{customer}}
+
+ Report Date: {{date}} + Prepared by: {{author}}
- {% endif %}
-
+ +
+
+ {% if cloud_status_quo_html %}{{ cloud_status_quo_html | safe }}{% endif %} +
+
+ + +
+

Quick Navigation

+ +
+ + +

Executive Summary

+

- The purpose of this report is to highlight the assessment findings for {{customer}}. The findings below are - representative of the cloud accounts and hosts that were in scope of the engagement and cover cloud compliance - and vulnerability findings leveraging FortiCNAPP agentless scanning capabilities. This report provides a - detailed summary of each identified area of interest and how it pertains to your overall cloud security and - risk. -

-

- Below is a summary of findings. Additional detail is provided on subsequent pages: + This Cloud Security Assessment provides a comprehensive analysis of {{customer}}'s security posture across cloud infrastructure, workloads, and compliance frameworks. The findings below represent actionable intelligence to strengthen your security position.

-
- {% if container_vulns_data %} -
-
-

Total Containers with Critical Vulnerabilities

-
-
- {{container_vulns_data.critical_vuln_count}} -
+ +
+ {% if host_vulns_data %} +
+
{{host_vulns_data.critical_vuln_count}}
+
Hosts with Critical Vulnerabilities
{% endif %} - {% if host_vulns_data %} -
-
-

Hosts with Critical Vulnerabilities

-
-
- {{host_vulns_data.critical_vuln_count}} -
+ {% if container_vulns_data %} +
+
{{container_vulns_data.critical_vuln_count}}
+
Containers with Critical Vulnerabilities
{% endif %} {% if aws_compliance_data %} -
-
-

Total Critical AWS Compliance Findings

-
-
- {{aws_compliance_data.critical_finding_count}} -
+
+
{{aws_compliance_data.critical_finding_count}}
+
Critical AWS Compliance Findings
- {% endif %} {% if azure_compliance_data %} -
-
-

Total Critical Azure Compliance Findings

-
-
- {{azure_compliance_data.critical_finding_count}} -
+
+
{{azure_compliance_data.critical_finding_count}}
+
Critical Azure Compliance Findings
- {% endif %} {% if gcp_compliance_data %} -
-
-

Total Critical GCP Compliance Findings

-
-
- {{gcp_compliance_data.critical_finding_count}} -
+
+
{{gcp_compliance_data.critical_finding_count}}
+
Critical GCP Compliance Findings
{% endif %} {% if alerts_data %} -
-
-

Total High / Critical Behaviors Detected

-
-
- {{ alerts_data.high_critical_finding_count}} -
+
+
{{ alerts_data.high_critical_finding_count }}
+
High/Critical Behavioral Alerts
{% endif %} {% if secrets_data %} -
-
-

Number of Secrets Detected

-
-
- {{ secrets_data.secrets_count}} -
+
+
{{ secrets_data.secrets_count }}
+
Exposed Secrets Detected
+
+ {% endif %} + + {% if ciem_data and ciem_data.AZURE and ciem_data.AZURE.high_risk_count_80 is defined and ciem_data.AZURE.high_risk_count_80 > 0 %} +
+
{{ ciem_data.AZURE.high_risk_count_80 }}
+
Azure High Privilege Identities
(≥80 unused entitlements)
{% endif %}
-
-

- - This assessment offers a glimpse into the value that FortiCNAPP provides customers, including: - -

-

-

    -
  • Reduce alerts 100:1
  • -
  • Speed up security investigations by 80%
  • -
  • Decrease SIEM ingestion costs by 50%
  • -
  • Improve detections of anomalous behaviors in cloud accounts and workloads
  • -
  • Accelerate security throughout development with less effort
  • -
-

- {% if recommendations %} -
- {{ recommendations | safe }} + + {% if pdf %} +
+ {% if polygraph_graphic_html %}{{ polygraph_graphic_html | safe }}{% endif %} +
+ {% else %} +
+ {% if polygraph_graphic_html %}{{ polygraph_graphic_html | safe }}{% endif %}
{% endif %} +
+ + +
+
+

Immediate Actions Required

+

Based on the assessment findings, the following actions are prioritized by severity and impact:

+ +
    + {% if secrets_data and secrets_data.secrets_count > 0 %} +
  1. + 1 +
    + Rotate Exposed Secrets Immediately +

    {{secrets_data.secrets_count}} secret(s) have been detected in your workloads. Rotate these credentials and investigate potential unauthorized access.

    +
    +
  2. + {% endif %} + {% if host_vulns_data and host_vulns_data.critical_vuln_count > 0 %} +
  3. + {% if secrets_data and secrets_data.secrets_count > 0 %}2{% else %}1{% endif %} +
    + Patch Critical Host Vulnerabilities +

    {{host_vulns_data.critical_vuln_count}} host(s) have critical vulnerabilities with available patches. Prioritize patching systems with network exposure.

    +
    +
  4. + {% endif %} -
-
- {% if secrets_data.secrets_count > 0 %} -
+ {% if container_vulns_data and container_vulns_data.critical_vuln_count > 0 %} +
  • + {% set n = 1 %}{% if secrets_data and secrets_data.secrets_count > 0 %}{% set n = n + 1 %}{% endif %}{% if host_vulns_data and host_vulns_data.critical_vuln_count > 0 %}{% set n = n + 1 %}{% endif %}{{n}} +
    + Rebuild Vulnerable Container Images +

    {{container_vulns_data.critical_vuln_count}} container image(s) contain critical vulnerabilities. Update base images and redeploy affected workloads.

    +
    +
  • + {% endif %} -

    Exposed SSH Keys

    -

    - Using FortiCNAPP agentless workload scanning the following SSH Keys have been found on your workloads: -

    + {% if aws_compliance_data and aws_compliance_data.critical_finding_count > 0 %} +
  • + {% set n = 1 %}{% if secrets_data and secrets_data.secrets_count > 0 %}{% set n = n + 1 %}{% endif %}{% if host_vulns_data and host_vulns_data.critical_vuln_count > 0 %}{% set n = n + 1 %}{% endif %}{% if container_vulns_data and container_vulns_data.critical_vuln_count > 0 %}{% set n = n + 1 %}{% endif %}{{n}} +
    + Address AWS Compliance Gaps +

    {{aws_compliance_data.critical_finding_count}} critical compliance finding(s) detected. Review IAM policies, encryption settings, and network configurations.

    +
    +
  • + {% endif %} - {{ secrets_data.secrets_raw.to_html(index=False) | safe }} + {% if azure_compliance_data and azure_compliance_data.critical_finding_count > 0 %} +
  • + {% set n = 1 %}{% if secrets_data and secrets_data.secrets_count > 0 %}{% set n = n + 1 %}{% endif %}{% if host_vulns_data and host_vulns_data.critical_vuln_count > 0 %}{% set n = n + 1 %}{% endif %}{% if container_vulns_data and container_vulns_data.critical_vuln_count > 0 %}{% set n = n + 1 %}{% endif %}{% if aws_compliance_data and aws_compliance_data.critical_finding_count > 0 %}{% set n = n + 1 %}{% endif %}{{n}} +
    + Address Azure Compliance Gaps +

    {{azure_compliance_data.critical_finding_count}} critical compliance finding(s) detected. Review Azure AD, storage security, and network security groups.

    +
    +
  • + {% endif %} + + {% if alerts_data and alerts_data.high_critical_finding_count > 0 %} +
  • + {% set n = 1 %}{% if secrets_data and secrets_data.secrets_count > 0 %}{% set n = n + 1 %}{% endif %}{% if host_vulns_data and host_vulns_data.critical_vuln_count > 0 %}{% set n = n + 1 %}{% endif %}{% if container_vulns_data and container_vulns_data.critical_vuln_count > 0 %}{% set n = n + 1 %}{% endif %}{% if aws_compliance_data and aws_compliance_data.critical_finding_count > 0 %}{% set n = n + 1 %}{% endif %}{% if azure_compliance_data and azure_compliance_data.critical_finding_count > 0 %}{% set n = n + 1 %}{% endif %}{{n}} +
    + Investigate Behavioral Alerts +

    {{alerts_data.high_critical_finding_count}} high/critical behavioral alert(s) require investigation for potential security incidents or policy violations.

    +
    +
  • + {% endif %} +
  • + + +
    + Schedule Regular Assessments +

    Implement continuous security monitoring and schedule recurring assessments to track remediation progress and detect new risks.

    +
    +
  • +
    - {% endif %} -
    - {% if aws_compliance_data or azure_compliance_data or gcp_compliance_data %} -

    Compliance Findings

    -

    Using FortiCNAPP agentless compliance functionality, we’ve assessed the current security posture against best - practices, policies, and compliance frameworks. FortiCNAPP identified the following:

    - {% endif %} +
    + + +{% if secrets_data and secrets_data.secrets_count > 0 %} +
    +

    Exposed Secrets CRITICAL

    + +
    +
    !
    +
    + Immediate Action Required + The following SSH keys and secrets have been detected in your workloads. These should be rotated immediately and the source of exposure investigated. +
    +
    + + {{ secrets_data.secrets_raw.to_html(index=False) | safe }} + +
    +

    Remediation Steps

    +
      +
    1. + 1 +
      + Rotate all exposed credentials +

      Generate new SSH keys and update all systems using the compromised keys.

      +
      +
    2. +
    3. + 2 +
      + Audit access logs +

      Review authentication logs for any unauthorized access using the exposed credentials.

      +
      +
    4. +
    5. + 3 +
      + Implement secrets management +

      Use a secrets manager (HashiCorp Vault, AWS Secrets Manager) to prevent future exposure.

      +
      +
    6. +
    7. + 4 +
      + Review File Permissions (DAC – Discretionary Access Control) +

      Verify and restrict file permissions on all secret files to ensure only authorized users and processes can access them. Implement least-privilege access controls using appropriate file permissions (e.g., 600 for private keys).

      +
      +
    8. +
    +
    +
    +{% endif %} - {% if aws_compliance_data and aws_compliance_data.summary_count > 0 %} -
    -

    AWS Compliance Findings

    -

    - Total AWS Accounts Analyzed: {{aws_compliance_data.cloud_accounts_count}} - + +{% if aws_compliance_data or azure_compliance_data or gcp_compliance_data %} +

    +

    Compliance Findings

    - {{ aws_compliance_data.compliance_summary.to_html(index=False) | safe }} - {{ aws_compliance_data.compliance_findings_by_service_bar_graphic | safe }} - {{ aws_compliance_data.compliance_findings_by_account_bar_graphic | safe }} -

    -
    - {% endif %} +

    + FortiCNAPP has assessed your cloud security posture against CIS benchmarks and industry best practices. The following findings highlight areas requiring attention to maintain compliance and reduce risk. +

    - {% if azure_compliance_data and azure_compliance_data.summary_count > 0 %} -
    + {% if aws_compliance_data and aws_compliance_data.summary_count > 0 %} +
    +
    +
    AWS Compliance Assessment
    +
    +
    +

    Total AWS Accounts Analyzed: {{aws_compliance_data.cloud_accounts_count}}

    + {{ aws_compliance_data.compliance_summary.to_html(index=False) | safe }} + {{ aws_compliance_data.compliance_findings_by_service_bar_graphic | safe }} + {{ aws_compliance_data.compliance_findings_by_account_bar_graphic | safe }} +
    +
    + {% endif %} -

    -

    Azure Compliance Findings

    - - Total Azure Subscriptions Analyzed: {{azure_compliance_data.cloud_accounts_count}} - + {% if azure_compliance_data and azure_compliance_data.summary_count > 0 %} +
    +
    +
    Azure Compliance Assessment
    +
    +
    +

    Total Azure Subscriptions Analyzed: {{azure_compliance_data.cloud_accounts_count}}

    {{ azure_compliance_data.compliance_summary.to_html(index=False) | safe }} {{ azure_compliance_data.compliance_findings_by_service_bar_graphic | safe }} {{ azure_compliance_data.compliance_findings_by_account_bar_graphic | safe }} -

    -
    - {% endif %} +
    + {% endif %} - {% if gcp_compliance_data and gcp_compliance_data.summary_count > 0 %} -
    -

    -

    GCP Compliance Findings

    - - Total GCP Subscriptions Analyzed: {{gcp_compliance_data.cloud_accounts_count}} - + {% if gcp_compliance_data and gcp_compliance_data.summary_count > 0 %} +
    +
    +
    GCP Compliance Assessment
    +
    +
    +

    Total GCP Projects Analyzed: {{gcp_compliance_data.cloud_accounts_count}}

    {{ gcp_compliance_data.compliance_summary.to_html(index=False) | safe }} {{ gcp_compliance_data.compliance_findings_by_service_bar_graphic | safe }} {{ gcp_compliance_data.compliance_findings_by_account_bar_graphic | safe }} -

    - {% endif %}
    - {% if alerts_data %} -
    - -

    Behavioral & Known-Bad Alerts - Polygraph Anomalies (last 7 days / top 25)

    -

    - Using the FortiCNAPP agentless Cloud Log behavioral assessment & any behavioral data from any agents you may - have deployed, we’ve identified the following anomalous or policy-based activity for further investigation. -

    + {% endif %} + +{% endif %} + + +{% if alerts_data %} +
    +

    Security Alerts & Anomalies

    + +
    +
    i
    +
    + Behavioral Analysis (Last 7 Days) + FortiCNAPP has identified {{alerts_data.high_critical_finding_count}} high/critical behavioral anomalies that may indicate security incidents, policy violations, or misconfigurations requiring investigation. +
    +
    - {{ alerts_data.alerts_raw.to_html(index=False) | safe }} +

    Top 25 Alerts by Severity

    + {{ alerts_data.alerts_raw.to_html(index=False) | safe }} + + {% if alerts_data.high_critical_finding_count > 0 %} +
    +

    Investigation Guidance

    +
      +
    1. + 1 +
      + Review Critical/High Alerts First +

      Focus on alerts indicating potential compromise, data exfiltration, or privilege escalation.

      +
      +
    2. +
    3. + 2 +
      + Correlate with Access Logs +

      Cross-reference alert timestamps with CloudTrail, Azure Activity Logs, or GCP Audit Logs.

      +
      +
    4. +
    5. + 3 +
      + Update Detection Rules +

      Tune FortiCNAPP policies to reduce false positives and enhance detection accuracy.

      +
      +
    6. +
    {% endif %} -
    - {% if host_vulns_data or container_vulns_data %} - -

    Workload Vulnerability Assessment

    -

    - FortiCNAPP has scanned and identified vulnerable container images and/or hosts and associated risk of the - vulnerabilities present. If the FortiCNAPP agent was not installed as part of this assessment it may be - installed later to highlight observed behavior, communication paths, and context. -

    +
    +{% endif %} - {% if host_vulns_data %} -

    - Total Hosts Scanned: {{host_vulns_data.hosts_scanned_count}} -

    + +{% if ciem_data %} +
    +

    Cloud Identity & Entitlement Management (CIEM)

    - {% endif %} - - {% if container_vulns_data %} +

    + Analysis of cloud identities with excessive privileges and unused entitlements across AWS, Azure, and GCP environments. Identities with high unused permissions represent a significant security risk and should be reviewed for least privilege adherence. +

    -

    - Total Container Images Scanned: {{container_vulns_data.containers_scanned_count}} -

    -
    + +
    + {% if ciem_data.AWS and ciem_data.AWS.high_privilege_count is defined %} +
    +
    {{ ciem_data.AWS.high_privilege_count }}
    +
    AWS High Privilege Identities
    +
    ≥{{ ciem_data.AWS.threshold }} unused entitlements
    +
    +
    +
    {{ ciem_data.AWS.total_identities }}
    +
    Total AWS Identities Analyzed
    +
    {{ ciem_data.AWS.root_count }} with root access
    +
    + {% endif %} + {% if ciem_data.AZURE and ciem_data.AZURE.high_privilege_count is defined %} +
    +
    {{ ciem_data.AZURE.high_privilege_count }}
    +
    Azure High Privilege Identities
    +
    ≥{{ ciem_data.AZURE.threshold }} unused entitlements
    +
    +
    +
    {{ ciem_data.AZURE.total_identities }}
    +
    Total Azure Identities Analyzed
    +
    {{ ciem_data.AZURE.root_count }} with root access
    +
    {% endif %} + {% if ciem_data.GCP and ciem_data.GCP.high_privilege_count is defined %} +
    +
    {{ ciem_data.GCP.high_privilege_count }}
    +
    GCP High Privilege Identities
    +
    ≥{{ ciem_data.GCP.threshold }} unused entitlements
    +
    +
    +
    {{ ciem_data.GCP.total_identities }}
    +
    Total GCP Identities Analyzed
    +
    {{ ciem_data.GCP.root_count }} with root access
    +
    {% endif %} +
    + + {% for provider in ['AWS', 'AZURE', 'GCP'] %} + {% if ciem_data[provider] and ciem_data[provider].high_privilege_count is defined %} - {% if host_vulns_data %} + {% if ciem_data[provider].high_privilege_count > 0 %} +

    {{ provider }} High Privilege Identities (≥{{ ciem_data[provider].threshold }} Unused Entitlements)

    -

    Host Vulnerability Summary

    - {{ host_vulns_data.host_vulns_summary.to_html(index=False) | safe }} - {{ host_vulns_data.host_vulns_summary_bar_graphic | safe }} +
    +
    !
    +
    + High Privilege Identities Detected + {{ ciem_data[provider].high_privilege_count }} {{ provider }} identities have ≥{{ ciem_data[provider].threshold }} unused entitlements. These over-provisioned identities should be reviewed for least privilege compliance. +
    +
    + + + + + + + + + + + + + {% for row in ciem_data[provider].high_privilege_identities.itertuples() %} + + + + + + + + + {% endfor %} + +
    Principal IDNameAdminMFAUnused EntitlementsRisk
    {{ row.PRINCIPAL_ID[:40] }}...{{ row.NAME }} + {% if row.has_full_admin %} + + {% else %} + - + {% endif %} + + + {{ row.mfa_status }} + + + {{ row.unused_count }} / {{ row.total_count }} +
    ({{ row.unused_percentage }}%) +
    + + {{ row.risk_severity }} + +
    +

    + Highlighted rows indicate identities with full administrative access. + MFA status: Enabled = MFA configured, + Disabled = No MFA, + Unknown = Status not determined. +

    {% endif %} -
    - {% if container_vulns_data %} -

    Container Vulnerability Summary

    - {{ container_vulns_data.container_vulns_summary.to_html(index=False) | safe }} - {{ container_vulns_data.container_vulns_summary_by_package_bar_graphic | safe }} + + {% if ciem_data[provider].root_count > 0 and ciem_data[provider].root_count > ciem_data[provider].critical_count %} +

    {{ provider }} Identities with Root/Admin Access

    +

    + Found {{ ciem_data[provider].root_count }} identities with full administrative privileges. + {% if ciem_data[provider].critical_count == 0 %} + None have ≥{{ ciem_data[provider].threshold }} unused entitlements. + {% endif %} +

    + {% endif %} + + {% if ciem_data[provider].high_privilege_count > 0 and ciem_data[provider].high_privilege_count > ciem_data[provider].critical_count %} +

    {{ provider }} Identities with Excessive Unused Entitlements

    +

    + Found {{ ciem_data[provider].high_privilege_count }} identities with ≥{{ ciem_data[provider].threshold }} unused entitlements. + {% if ciem_data[provider].critical_count == 0 %} + None have full administrative access. + {% endif %} +

    {% endif %} -
    - {% if host_vulns_data or container_vulns_data or aws_compliance_data or azure_compliance_data or - gcp_compliance_data%} -
    -

    Appendix of Detailed Findings

    -

    This section contains additional details on the findings that were summarized above:

    - {% if host_vulns_data or container_vulns_data %} -

    Detailed CVE Breakdown

    + + + {% if ciem_data[provider].all_identities is defined and not ciem_data[provider].all_identities.empty %} +

    {{ provider }} Top Identity Risks (by Unused Entitlements)

    + + + + + + + + + + + + + + + {% for row in ciem_data[provider].all_identities.itertuples() %} + + + + + + + + + + {% endfor %} + +
    Principal IDNameAdminMFAUnused %Unused/TotalRisk
    {{ row.PRINCIPAL_ID[:30] }}...{{ row.NAME }} + {% if row.has_full_admin %} + + {% else %} + - + {% endif %} + + + {{ row.mfa_status }} + + + {{ row.unused_percentage }}% + + {{ row.unused_count }} / {{ row.total_count }} + + + {{ row.risk_severity }} + +
    +

    + Showing top 25 identities sorted by risk. Identities with highlighted rows have both admin access and ≥{{ ciem_data[provider].threshold }} unused entitlements. +

    {% endif %} - {% if host_vulns_data and not host_vulns_data.fixable_vulns.empty %} -

    Hosts With Critical, Fixable Vulnerabilities

    -

    This table lists all hosts with "critical" vulnerabilities that have fixes available. Additional - vulnerability information for other severity levels - can be found in the FortiCNAPP UI.

    - {{ host_vulns_data.fixable_vulns.to_html() | safe }} {% endif %} + {% endfor %} + + + {% set total_high_privilege = (ciem_data.AWS.high_privilege_count if ciem_data.AWS and ciem_data.AWS.high_privilege_count is defined else 0) + (ciem_data.AZURE.high_privilege_count if ciem_data.AZURE and ciem_data.AZURE.high_privilege_count is defined else 0) + (ciem_data.GCP.high_privilege_count if ciem_data.GCP and ciem_data.GCP.high_privilege_count is defined else 0) %} + {% if total_high_privilege > 0 %} +
    +

    Recommended Actions

    +
      +
    1. + 1 +
      + Enable MFA on All Identities +

      Immediately enable Multi-Factor Authentication (MFA) for all identities, especially those with administrative access or high unused entitlements. MFA significantly reduces the risk of credential compromise.

      +
      +
    2. +
    3. + 2 +
      + Reduce Unused Entitlements (≥70) +

      Review and remove unused permissions from identities with ≥70 unused entitlements. These over-provisioned identities violate least privilege principles and increase attack surface.

      +
      +
    4. +
    5. + 3 +
      + Review Administrative Access +

      Audit all identities with full administrative access. Remove admin privileges where not absolutely necessary and implement role-based access control (RBAC).

      +
      +
    6. +
    7. + 4 +
      + Implement Least Privilege Policies +

      Apply the principle of least privilege across all identities. Grant only the minimum permissions required for each role and regularly review access rights.

      +
      +
    8. +
    9. + 5 +
      + Enable Just-In-Time (JIT) Access +

      Implement temporary privilege escalation for administrative tasks instead of permanent admin access. Use JIT access mechanisms to reduce standing privileges.

      +
      +
    10. +
    +
    + {% endif %} + +{% endif %} - {% if container_vulns_data and not container_vulns_data.fixable_vulns.empty %} + +{% if host_vulns_data or container_vulns_data %} +
    +

    Workload Vulnerability Assessment

    -

    Containers With Critical, Fixable Vulnerabilities

    -

    This table lists all containers with "critical" vulnerabilities that have fixes available. Additional - vulnerability information can be found in the FortiCNAPP UI.

    - {{ container_vulns_data.fixable_vulns.to_html() | safe }} +

    + FortiCNAPP has performed agentless vulnerability scanning across your workloads. The following findings identify vulnerable hosts and container images with associated remediation guidance. +

    +
    + {% if host_vulns_data %} +
    +
    {{host_vulns_data.hosts_scanned_count}}
    +
    Total Hosts Scanned
    +
    + {% endif %} + {% if container_vulns_data %} +
    +
    {{container_vulns_data.containers_scanned_count}}
    +
    Container Images Scanned
    +
    {% endif %} +
    -
    - {% if aws_compliance_data or azure_compliance_data or gcp_compliance_data %} + {% if host_vulns_data %} +

    Host Vulnerability Summary

    + {{ host_vulns_data.host_vulns_summary.to_html(index=False) | safe }} + {{ host_vulns_data.host_vulns_summary_bar_graphic | safe }} + + {% if host_vulns_data.critical_vuln_count > 0 %} +
    +
    !
    +
    + Critical Vulnerabilities Detected + {{host_vulns_data.critical_vuln_count}} host(s) have critical vulnerabilities. Prioritize patching based on network exposure and data sensitivity. +
    +
    + {% endif %} -

    Detailed Cloud Compliance Findings

    -

    - {% if aws_compliance_data %} -

    AWS - Top High/Critical Compliance Findings

    -

    - {{ aws_compliance_data.compliance_detail.to_html() | safe }} - - {% if aws_compliance_data.critical_finding_count > 0 %} -

    AWS - Critical Findings with Details

    -

    This table contains CIS compliance findings with a severity of "Critical". Other severity levels can be - reviewed in the FortiCNAPP UI.

    - {{ aws_compliance_data.critical_details.to_html().replace("\\n","
    ") | safe }} - {% endif %} - {% endif %} + {% if host_vulns_data.all_cves_detail is defined and not host_vulns_data.all_cves_detail.empty %} +

    Detailed CVE List (High, Medium, Low Severity)

    +
    +
    i
    +
    + Additional Vulnerabilities + The table below shows all High, Medium, and Low severity CVEs found across your hosts. Review and prioritize based on your environment and risk tolerance. +
    +
    + {{ host_vulns_data.all_cves_detail.to_html(index=False) | safe }} + {% endif %} + {% endif %} - {% if azure_compliance_data %} -

    Azure - Top High/Critical Compliance Findings

    -

    - {{ azure_compliance_data.compliance_detail.to_html(index=False) | safe }} - - {% if azure_compliance_data.critical_finding_count > 0 %} -

    Azure - Critical Findings with Details

    -

    This table contains CIS compliance findings with a severity of "Critical". Other severity levels can be - reviewed in the FortiCNAPP UI.

    - {{ azure_compliance_data.critical_details.to_html().replace("\\n","
    ") | safe }} - {% endif %} - {% endif %} + {% if container_vulns_data %} +

    Container Vulnerability Summary

    + {{ container_vulns_data.container_vulns_summary.to_html(index=False) | safe }} + {{ container_vulns_data.container_vulns_summary_by_package_bar_graphic | safe }} + + {% if container_vulns_data.critical_vuln_count > 0 %} +
    +
    !
    +
    + Vulnerable Container Images + {{container_vulns_data.critical_vuln_count}} container image(s) contain critical vulnerabilities. Update base images and rebuild affected containers. +
    +
    + {% endif %} + {% endif %} +
    +{% endif %} + + +{% if host_vulns_data or container_vulns_data or aws_compliance_data or azure_compliance_data or gcp_compliance_data %} +
    +

    Appendix: Detailed Findings

    +

    This section contains granular details for remediation teams.

    + + {% if host_vulns_data or container_vulns_data %} +

    Detailed CVE Breakdown

    + + {% if host_vulns_data and not host_vulns_data.fixable_vulns.empty %} +

    Hosts with Critical, Fixable Vulnerabilities (CVE Risk Score = 10)

    +
    +
    i
    +
    + Quick Win Opportunity + These vulnerabilities have fixes available. Patching these systems will significantly reduce your attack surface. +
    +
    +
    +
    📊
    +
    + Understanding Risk Score +

    + This report displays Critical vulnerabilities with CVSS score 10.0 (the highest severity). + The Risk Score shown represents the vulnerability impact, which is calculated based on: +

    +
      +
    • Number of hosts or container images affected - More affected systems = higher risk
    • +
    • Number of packages affected - Widespread package vulnerabilities = higher risk
    • +
    +

    + Note: Risk Score differs from CVSS score. CVSS measures vulnerability severity, while Risk Score measures blast radius and organizational impact. +

    +
    +
    + {{ host_vulns_data.fixable_vulns.to_html() | safe }} + +
    +

    Host Vulnerability Recommendations

    +
      +
    1. + 1 +
      + Prioritize IDENTIFYRESPOND +

      High CVSS, public exploits, Internet-exposed, and critical hosts first.

      +
      +
    2. +
    3. + 2 +
      + Patch quickly PROTECT +

      OS & software updates; automate where possible; test critical patches.

      +
      +
    4. +
    5. + 3 +
      + Reduce attack surface PROTECT +

      Remove unused software/services, close unnecessary ports, harden configs.

      +
      +
    6. +
    7. + 4 +
      + Secure network & access PROTECT +

      Firewalls, restricted SSH/VPN, segmentation; monitor exposed ports.

      +
      +
    8. +
    9. + 5 +
      + Continuous vulnerability scanning DETECT +

      Regularly scan hosts, confirm remediation, focus on exploitable + Internet-exposed hosts.

      +
      +
    10. +
    11. + 6 +
      + Monitor & detect DETECT +

      Use IDS/EDR; watch logs for suspicious activity.

      +
      +
    12. +
    13. + 7 +
      + Backup & document RECOVER +

      Backup before patching; track remediation; include exposure in reports.

      +
      +
    14. +
    +
    + {% endif %} - {% if gcp_compliance_data %} -

    GCP - Top High/Critical Compliance Findings

    -

    - {{ gcp_compliance_data.compliance_detail.to_html(index=False) | safe }} - - {% if gcp_compliance_data.critical_finding_count > 0 %} -

    GCP - Critical Findings with Details

    -

    This table contains CIS compliance findings with a severity of "Critical". Other severity levels can be - reviewed in the FortiCNAPP UI.

    - {{ gcp_compliance_data.critical_details.to_html().replace("\\n","
    ") | safe }} - {% endif %} - {% endif %} + {% if container_vulns_data and not container_vulns_data.fixable_vulns.empty %} +

    Containers with Critical, Fixable Vulnerabilities (CVE Risk Score = 10)

    +
    +
    i
    +
    + Image Update Required + Update the base images for these containers and rebuild to remediate the vulnerabilities. +
    +
    +
    +
    📊
    +
    + Understanding Risk Score +

    + This report displays Critical vulnerabilities with CVSS score 10.0 (the highest severity). + The Risk Score shown represents the vulnerability impact, which is calculated based on: +

    +
      +
    • Number of container images affected - More affected images = higher risk
    • +
    • Number of packages affected - Widespread package vulnerabilities = higher risk
    • +
    +

    + Note: Risk Score differs from CVSS score. CVSS measures vulnerability severity, while Risk Score measures blast radius and organizational impact. +

    +
    +
    + {{ container_vulns_data.fixable_vulns.to_html() | safe }} + {% endif %} + {% endif %} - {% endif %} + {% if aws_compliance_data or azure_compliance_data or gcp_compliance_data %} +

    Detailed Cloud Compliance Findings

    + + {% if aws_compliance_data %} +

    AWS - High/Critical Compliance Findings

    + {{ aws_compliance_data.compliance_detail.to_html() | safe }} + + {% if aws_compliance_data.critical_finding_count > 0 %} +

    AWS - Critical Findings with Remediation Details

    + {{ aws_compliance_data.critical_details.to_html().replace("\\n","
    ") | safe }} + {% endif %} + {% endif %} + + {% if azure_compliance_data %} +

    Azure - High/Critical Compliance Findings

    + {{ azure_compliance_data.compliance_detail.to_html(index=False) | safe }} + + {% if azure_compliance_data.critical_finding_count > 0 %} +

    Azure - Critical Findings with Remediation Details

    + {{ azure_compliance_data.critical_details.to_html().replace("\\n","
    ") | safe }} + {% endif %} + +
    +

    Cloud Compliance Recommendations

    +

    + Achieving and maintaining cloud compliance requires a systematic approach combining automation, continuous monitoring, and proactive remediation. Follow these best practices to strengthen your compliance posture: +

    +
      +
    1. + 1 +
      + Implement Continuous Compliance Monitoring DETECT +

      Deploy automated tools like FortiCNAPP to continuously scan cloud environments for configuration drift, policy violations, and compliance gaps. Real-time monitoring enables immediate detection and faster remediation of non-compliant resources before they become security risks.

      +
      +
    2. +
    3. + 2 +
      + Automate Remediation Workflows RESPOND +

      Configure automated responses for common compliance violations such as open security groups, unencrypted storage, or missing tags. Use Infrastructure-as-Code (IaC) to enforce compliant configurations and prevent manual misconfigurations.

      +
      +
    4. +
    5. + 3 +
      + Prioritize Critical & High Severity Findings IDENTIFYRESPOND +

      Address critical findings first, focusing on publicly exposed resources, data encryption gaps, IAM misconfigurations, and logging deficiencies. These pose the highest risk to your security posture and regulatory standing.

      +
      +
    6. +
    7. + 4 +
      + Establish Policy-as-Code PROTECT +

      Define compliance policies as code to enforce security standards across all cloud accounts and environments. Use tools like Terraform, CloudFormation, or Azure Policy to codify CIS benchmarks, NIST frameworks, and regulatory requirements.

      +
      +
    8. +
    9. + 5 +
      + Enable Comprehensive Audit Logging DETECT +

      Ensure CloudTrail (AWS), Activity Logs (Azure), and Cloud Audit Logs (GCP) are enabled across all regions and accounts. Centralize logs in a SIEM or log analytics platform for compliance reporting and forensic analysis.

      +
      +
    10. +
    11. + 6 +
      + Conduct Regular Compliance Assessments IDENTIFY +

      Schedule recurring compliance scans (weekly or monthly) to track remediation progress and identify new violations. Compare results over time to measure improvement and demonstrate compliance to auditors.

      +
      +
    12. +
    13. + 7 +
      + Implement Least Privilege Access PROTECT +

      Review and restrict IAM permissions to the minimum required for each role. Remove unused accounts, enforce MFA, rotate credentials regularly, and monitor privileged access with session recording and just-in-time access.

      +
      +
    14. +
    15. + 8 +
      + Document & Report Compliance Status IDENTIFY +

      Maintain compliance documentation including policies, procedures, remediation tracking, and audit reports. Generate executive dashboards showing compliance scores, trend analysis, and remediation velocity for stakeholders and auditors.

      +
      +
    16. +
    +
    +
    💡
    +
    + Continuous Compliance is Key +

    + Cloud environments are dynamic—resources are created, modified, and deleted constantly. Point-in-time compliance scans provide only a snapshot. + Continuous compliance monitoring ensures your security posture remains strong 24/7 by detecting drift immediately and enabling rapid response to new violations. +

    +

    + FortiCNAPP provides real-time compliance monitoring across AWS, Azure, and GCP with automated detection, prioritized remediation guidance, and executive reporting to maintain continuous compliance. +

    +
    +
    {% endif %} -
    + + {% if gcp_compliance_data %} +

    GCP - High/Critical Compliance Findings

    + {{ gcp_compliance_data.compliance_detail.to_html(index=False) | safe }} + + {% if gcp_compliance_data.critical_finding_count > 0 %} +

    GCP - Critical Findings with Remediation Details

    + {{ gcp_compliance_data.critical_details.to_html().replace("\\n","
    ") | safe }} + {% endif %} + {% endif %} + {% endif %} + +{% endif %} + + +
    + {% if recommendations %} +
    + {{ recommendations | safe }} +
    + {% else %} +
    +

    Recommendations & Next Steps

    +

    Based on the findings of this assessment, we recommend the following action plan:

    +
      +
    1. Immediate (0-7 days): Address all critical vulnerabilities and rotate any exposed secrets identified in this report.
    2. +
    3. Short-term (1-4 weeks): Remediate high-severity compliance findings and investigate behavioral alerts.
    4. +
    5. Medium-term (1-3 months): Implement continuous monitoring with FortiCNAPP and establish remediation workflows.
    6. +
    7. Ongoing: Schedule recurring Cloud Security Assessments to track improvement and detect new risks.
    8. +
    +

    Engage with your Fortinet account team and partner to review service offerings and prioritize remediation efforts.

    +
    + {% endif %} +
    + + +
    +

    Fortinet FortiCNAPP - One Platform

    +
    + {% if forticnapp_platform_html %}{{ forticnapp_platform_html | safe }}{% endif %} +
    +
    + + +
    +

    Fortinet Security Fabric

    +
    + {% if fortinet_sec_fabric_html %}{{ fortinet_sec_fabric_html | safe }}{% endif %} +
    +
    + + +
    +

    FortiCNAPP Security Outcomes

    +
    + {% if sec_outcomes_html %}{{ sec_outcomes_html | safe }}{% endif %} +
    +
    + + +
    +

    State of Cloud Security Report

    +
    + {% if state_of_cloud_html %}{{ state_of_cloud_html | safe }}{% endif %} +
    +
    + +