mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
chore(html): deprecate output (#3501)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
@@ -33,7 +33,6 @@ from prowler.lib.check.custom_checks_metadata import (
|
||||
from prowler.lib.cli.parser import ProwlerArgumentParser
|
||||
from prowler.lib.logger import logger, set_logging_config
|
||||
from prowler.lib.outputs.compliance.compliance import display_compliance_table
|
||||
from prowler.lib.outputs.html import add_html_footer, fill_html_overview_statistics
|
||||
from prowler.lib.outputs.json import close_json
|
||||
from prowler.lib.outputs.outputs import extract_findings_statistics
|
||||
from prowler.lib.outputs.summary_table import display_summary_table
|
||||
@@ -230,16 +229,6 @@ def prowler():
|
||||
args.output_directory,
|
||||
mode,
|
||||
)
|
||||
if mode == "html":
|
||||
add_html_footer(
|
||||
global_provider.output_options.output_filename,
|
||||
args.output_directory,
|
||||
)
|
||||
fill_html_overview_statistics(
|
||||
stats,
|
||||
global_provider.output_options.output_filename,
|
||||
args.output_directory,
|
||||
)
|
||||
# Send output to S3 if needed (-B / -D)
|
||||
if provider == "aws" and (
|
||||
args.output_bucket or args.output_bucket_no_assume
|
||||
|
||||
@@ -13,8 +13,6 @@ from prowler.providers.common.common import get_global_provider
|
||||
timestamp = datetime.today()
|
||||
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
|
||||
prowler_version = "3.14.0"
|
||||
html_logo_url = "https://github.com/prowler-cloud/prowler/"
|
||||
html_logo_img = "https://user-images.githubusercontent.com/3985464/113734260-7ba06900-96fb-11eb-82bc-d4f68a1e2710.png"
|
||||
square_logo_img = "https://user-images.githubusercontent.com/38561120/235905862-9ece5bd7-9aa3-4e48-807a-3a9035eb8bfb.png"
|
||||
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
|
||||
azure_logo = "https://user-images.githubusercontent.com/38561120/235927375-b23e2e0f-8932-49ec-b59c-d89f61c8041d.png"
|
||||
|
||||
@@ -127,9 +127,9 @@ Detailed documentation at https://docs.prowler.cloud
|
||||
"-M",
|
||||
"--output-modes",
|
||||
nargs="+",
|
||||
help="Output modes, by default csv, html and json",
|
||||
default=["csv", "json", "html", "json-ocsf"],
|
||||
choices=["csv", "json", "json-asff", "html", "json-ocsf"],
|
||||
help="Output modes, by default csv and json",
|
||||
default=["csv", "json", "json-ocsf"],
|
||||
choices=["csv", "json", "json-asff", "json-ocsf"],
|
||||
)
|
||||
common_outputs_parser.add_argument(
|
||||
"-F",
|
||||
|
||||
@@ -4,13 +4,11 @@ from typing import Any
|
||||
|
||||
from prowler.config.config import (
|
||||
csv_file_suffix,
|
||||
html_file_suffix,
|
||||
json_asff_file_suffix,
|
||||
json_file_suffix,
|
||||
json_ocsf_file_suffix,
|
||||
)
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.html import add_html_header
|
||||
from prowler.lib.outputs.models import (
|
||||
Check_Output_CSV_AWS_CIS,
|
||||
Check_Output_CSV_AWS_ISO27001_2013,
|
||||
@@ -46,8 +44,6 @@ def initialize_file_descriptor(
|
||||
|
||||
if output_mode in ("json", "json-asff", "json-ocsf"):
|
||||
file_descriptor.write("[")
|
||||
elif "html" in output_mode:
|
||||
add_html_header(file_descriptor, audit_info)
|
||||
else:
|
||||
# Format is the class model of the CSV format to print the headers
|
||||
csv_header = [x.upper() for x in generate_csv_fields(format)]
|
||||
@@ -95,13 +91,6 @@ def fill_file_descriptors(output_modes, output_directory, output_filename, provi
|
||||
)
|
||||
file_descriptors.update({output_mode: file_descriptor})
|
||||
|
||||
elif output_mode == "html":
|
||||
filename = f"{output_directory}/{output_filename}{html_file_suffix}"
|
||||
file_descriptor = initialize_file_descriptor(
|
||||
filename, output_mode, provider
|
||||
)
|
||||
file_descriptors.update({output_mode: file_descriptor})
|
||||
|
||||
elif provider.type == "gcp":
|
||||
if output_mode == "cis_2.0_gcp":
|
||||
filename = f"{output_directory}/compliance/{output_filename}_cis_2.0_gcp{csv_file_suffix}"
|
||||
|
||||
@@ -1,579 +0,0 @@
|
||||
import importlib
|
||||
import sys
|
||||
from os import path
|
||||
|
||||
from prowler.config.config import (
|
||||
html_file_suffix,
|
||||
html_logo_img,
|
||||
html_logo_url,
|
||||
prowler_version,
|
||||
timestamp,
|
||||
)
|
||||
from prowler.lib.check.models import Check_Report_AWS, Check_Report_GCP
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.outputs.models import (
|
||||
get_check_compliance,
|
||||
parse_html_string,
|
||||
unroll_dict,
|
||||
unroll_tags,
|
||||
)
|
||||
from prowler.lib.utils.utils import open_file
|
||||
|
||||
|
||||
def add_html_header(file_descriptor, provider):
|
||||
try:
|
||||
file_descriptor.write(
|
||||
"""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<!-- Required meta tags -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<style>
|
||||
.read-more {
|
||||
color: #00f;
|
||||
}
|
||||
|
||||
.bg-success-custom {
|
||||
background-color: #98dea7 !important;
|
||||
}
|
||||
|
||||
.bg-danger {
|
||||
background-color: #f28484 !important;
|
||||
}
|
||||
</style>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
|
||||
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
|
||||
<!-- https://datatables.net/download/index with jQuery, DataTables, Buttons, SearchPanes, and Select //-->
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.datatables.net/v/dt/jqc-1.12.4/dt-1.10.25/b-1.7.1/sp-1.4.0/sl-1.3.3/datatables.min.css" />
|
||||
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css"
|
||||
integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous" />
|
||||
<style>
|
||||
.show-read-more .more-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dataTable {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float: left !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<title>Prowler - The Handy Cloud Security Tool</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-4">
|
||||
<a href="""
|
||||
+ html_logo_url
|
||||
+ """><img class="float-left card-img-left mt-4 mr-4 ml-4"
|
||||
src="""
|
||||
+ html_logo_img
|
||||
+ """
|
||||
alt="prowler-logo"></a>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Report Information
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<b>Version:</b> """
|
||||
+ prowler_version
|
||||
+ """
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Parameters used:</b> """
|
||||
+ " ".join(sys.argv[1:])
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Date:</b> """
|
||||
+ timestamp.isoformat()
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div> """
|
||||
+ get_assessment_summary(provider)
|
||||
+ """
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Assessment Overview
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Total Findings:</b> TOTAL_FINDINGS
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Passed:</b> TOTAL_PASS
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Failed:</b> TOTAL_FAIL
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Total Resources:</b> TOTAL_RESOURCES
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-mt-3">
|
||||
<div class="col-md-12">
|
||||
<table class="table compact stripe row-border ordering" id="findingsTable" data-order='[[ 5, "asc" ]]' data-page-length='100'>
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Severity</th>
|
||||
<th scope="col">Service Name</th>
|
||||
<th scope="col">Region</th>
|
||||
<th style="width:20%" scope="col">Check ID</th>
|
||||
<th style="width:20%" scope="col">Check Title</th>
|
||||
<th scope="col">Resource ID</th>
|
||||
<th scope="col">Resource Tags</th>
|
||||
<th scope="col">Status Extended</th>
|
||||
<th scope="col">Risk</th>
|
||||
<th scope="col">Recomendation</th>
|
||||
<th scope="col">Compliance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"""
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def fill_html(file_descriptor, finding, output_options):
|
||||
try:
|
||||
row_class = "p-3 mb-2 bg-success-custom"
|
||||
if finding.status == "MANUAL":
|
||||
row_class = "table-info"
|
||||
elif finding.status == "FAIL":
|
||||
row_class = "table-danger"
|
||||
elif finding.status == "MUTED":
|
||||
row_class = "table-warning"
|
||||
file_descriptor.write(
|
||||
f"""
|
||||
<tr class="{row_class}">
|
||||
<td>{finding.status}</td>
|
||||
<td>{finding.check_metadata.Severity}</td>
|
||||
<td>{finding.check_metadata.ServiceName}</td>
|
||||
<td>{finding.location.lower() if isinstance(finding, Check_Report_GCP) else finding.region if isinstance(finding, Check_Report_AWS) else ""}</td>
|
||||
<td>{finding.check_metadata.CheckID.replace("_", "<wbr>_")}</td>
|
||||
<td>{finding.check_metadata.CheckTitle}</td>
|
||||
<td>{finding.resource_id.replace("<", "<").replace(">", ">").replace("_", "<wbr>_")}</td>
|
||||
<td>{parse_html_string(unroll_tags(finding.resource_tags))}</td>
|
||||
<td>{finding.status_extended.replace("<", "<").replace(">", ">").replace("_", "<wbr>_")}</td>
|
||||
<td><p class="show-read-more">{finding.check_metadata.Risk}</p></td>
|
||||
<td><p class="show-read-more">{finding.check_metadata.Remediation.Recommendation.Text}</p> <a class="read-more" href="{finding.check_metadata.Remediation.Recommendation.Url}"><i class="fas fa-external-link-alt"></i></a></td>
|
||||
<td><p class="show-read-more">{parse_html_string(unroll_dict(get_check_compliance(finding, finding.check_metadata.Provider, output_options)))}</p></td>
|
||||
</tr>
|
||||
"""
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def fill_html_overview_statistics(stats, output_filename, output_directory):
|
||||
try:
|
||||
filename = f"{output_directory}/{output_filename}{html_file_suffix}"
|
||||
# Read file
|
||||
if path.isfile(filename):
|
||||
with open(filename, "r") as file:
|
||||
filedata = file.read()
|
||||
|
||||
# Replace statistics
|
||||
# TOTAL_FINDINGS
|
||||
filedata = filedata.replace(
|
||||
"TOTAL_FINDINGS", str(stats.get("findings_count"))
|
||||
)
|
||||
# TOTAL_RESOURCES
|
||||
filedata = filedata.replace(
|
||||
"TOTAL_RESOURCES", str(stats.get("resources_count"))
|
||||
)
|
||||
# TOTAL_PASS
|
||||
filedata = filedata.replace("TOTAL_PASS", str(stats.get("total_pass")))
|
||||
# TOTAL_FAIL
|
||||
filedata = filedata.replace("TOTAL_FAIL", str(stats.get("total_fail")))
|
||||
# Write file
|
||||
with open(filename, "w") as file:
|
||||
file.write(filedata)
|
||||
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def add_html_footer(output_filename, output_directory):
|
||||
try:
|
||||
filename = f"{output_directory}/{output_filename}{html_file_suffix}"
|
||||
# Close HTML file if exists
|
||||
if path.isfile(filename):
|
||||
file_descriptor = open_file(
|
||||
filename,
|
||||
"a",
|
||||
)
|
||||
file_descriptor.write(
|
||||
"""
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Table search and paginator -->
|
||||
<!-- Optional JavaScript -->
|
||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
|
||||
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVJJAzGytlQcNXd"
|
||||
crossorigin="anonymous"></script>
|
||||
<!-- https://datatables.net/download/index with jQuery, DataTables, Buttons, SearchPanes, and Select //-->
|
||||
<script type="text/javascript"
|
||||
src="https://cdn.datatables.net/v/dt/jqc-1.12.4/dt-1.10.25/b-1.7.1/sp-1.4.0/sl-1.3.3/datatables.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// Initialise the table with 50 rows, and some search/filtering panes
|
||||
$('#findingsTable').DataTable({
|
||||
responsive: true,
|
||||
// Show 25, 50, 100 and All records
|
||||
lengthChange: true,
|
||||
lengthMenu: [[25, 50, 100, -1], [25, 50, 100, "All"]],
|
||||
searchPanes: {
|
||||
cascadePanes: true,
|
||||
viewTotal: true,
|
||||
},
|
||||
dom: 'Blfrtip',
|
||||
language: {
|
||||
// To enable a filter button instead of the filter row
|
||||
searchPanes: {
|
||||
clearMessage: 'Clear Filters',
|
||||
collapse: { 0: 'Filters', _: 'Filters (%d)' },
|
||||
initCollapsed: true
|
||||
|
||||
}
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
extend: 'searchPanes',
|
||||
config: {
|
||||
cascadePanes: true,
|
||||
viewTotal: true,
|
||||
orderable: false
|
||||
}
|
||||
}
|
||||
],
|
||||
columnDefs: [
|
||||
{
|
||||
searchPanes: {
|
||||
show: true,
|
||||
pagingType: 'numbers',
|
||||
searching: true
|
||||
},
|
||||
// Show all filters
|
||||
targets: [0, 1, 2, 3, 5, 7]
|
||||
}
|
||||
]
|
||||
});
|
||||
var maxLength = 30;
|
||||
// ReadMore ReadLess
|
||||
$(".show-read-more").each(function () {
|
||||
var myStr = $(this).text();
|
||||
if ($.trim(myStr).length > maxLength) {
|
||||
var newStr = myStr.substring(0, maxLength);
|
||||
var removedStr = myStr.substring(maxLength, $.trim(myStr).length);
|
||||
$(this).empty().html(newStr);
|
||||
$(this).append(' <a href="javascript:void(0);" class="read-more">read more...</a>');
|
||||
$(this).append('<span class="more-text">' + removedStr + '</span>');
|
||||
}
|
||||
});
|
||||
$(".read-more").click(function () {
|
||||
$(this).siblings(".more-text").contents().unwrap();
|
||||
$(this).remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
"""
|
||||
)
|
||||
file_descriptor.close()
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_aws_html_assessment_summary(provider):
|
||||
try:
|
||||
if provider.type == "aws":
|
||||
profile = (
|
||||
provider.identity.profile
|
||||
if provider.identity.profile is not None
|
||||
else "default"
|
||||
)
|
||||
if isinstance(provider.identity.audited_regions, list):
|
||||
audited_regions = " ".join(provider.identity.audited_regions)
|
||||
elif not provider.identity.audited_regions:
|
||||
audited_regions = "All Regions"
|
||||
# TODO: why this fallback?
|
||||
else:
|
||||
audited_regions = ", ".join(provider.identity.audited_regions)
|
||||
return (
|
||||
"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
AWS Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>AWS Account:</b> """
|
||||
+ provider.identity.account
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>AWS-CLI Profile:</b> """
|
||||
+ profile
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Audited Regions:</b> """
|
||||
+ audited_regions
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
AWS Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>User Id:</b> """
|
||||
+ provider.identity.user_id
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Caller Identity ARN:</b> """
|
||||
+ provider.identity.identity_arn
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_azure_html_assessment_summary(provider):
|
||||
try:
|
||||
if provider.type == "azure":
|
||||
printed_subscriptions = []
|
||||
for key, value in provider.identity.subscriptions.items():
|
||||
intermediate = f"{key} : {value}"
|
||||
printed_subscriptions.append(intermediate)
|
||||
|
||||
# check if identity is str(coming from SP) or dict(coming from browser or)
|
||||
if isinstance(provider.identity.identity_id, dict):
|
||||
html_identity = provider.identity.identity_id.get(
|
||||
"userPrincipalName", "Identity not found"
|
||||
)
|
||||
else:
|
||||
html_identity = provider.identity.identity_id
|
||||
return (
|
||||
"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Azure Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Azure Tenant IDs:</b> """
|
||||
+ " ".join(provider.identity.tenant_ids)
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Tenant Domain:</b> """
|
||||
+ provider.identity.domain
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Subscriptions:</b> """
|
||||
+ " ".join(printed_subscriptions)
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Azure Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Azure Identity Type:</b> """
|
||||
+ provider.identity.identity_type
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Identity ID:</b> """
|
||||
+ html_identity
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_gcp_html_assessment_summary(provider):
|
||||
try:
|
||||
if provider.type == "gcp":
|
||||
return (
|
||||
"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
GCP Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GCP Project IDs:</b> """
|
||||
+ ", ".join(provider.project_ids)
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
GCP Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GCP Account:</b> """
|
||||
+ provider.identity.profile
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_kubernetes_html_assessment_summary(provider):
|
||||
try:
|
||||
if provider.type == "kubernetes":
|
||||
return (
|
||||
"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Kubernetes Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes Context:</b> """
|
||||
+ provider.identity.context
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Kubernetes Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes Cluster:</b> """
|
||||
+ provider.identity.cluster
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes User:</b> """
|
||||
+ provider.identity.user
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}] -- {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_assessment_summary(provider):
|
||||
"""
|
||||
get_assessment_summary gets the HTML assessment summary for the provider
|
||||
"""
|
||||
try:
|
||||
# Dynamically get the Provider quick inventory handler
|
||||
provider_html_assessment_summary_function = (
|
||||
f"get_{provider.type}_html_assessment_summary"
|
||||
)
|
||||
return getattr(
|
||||
importlib.import_module(__name__), provider_html_assessment_summary_function
|
||||
)(provider)
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
sys.exit(1)
|
||||
@@ -264,15 +264,6 @@ def unroll_dict_to_list(dict: dict):
|
||||
return dict_list
|
||||
|
||||
|
||||
def parse_html_string(str: str):
|
||||
string = ""
|
||||
for elem in str.split(" | "):
|
||||
if elem:
|
||||
string += f"\n•{elem}\n"
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def parse_json_tags(tags: list):
|
||||
dict_tags = {}
|
||||
if tags and tags != [{}] and tags != [None]:
|
||||
|
||||
@@ -9,7 +9,6 @@ from prowler.lib.outputs.compliance.compliance import (
|
||||
fill_compliance,
|
||||
)
|
||||
from prowler.lib.outputs.file_descriptors import fill_file_descriptors
|
||||
from prowler.lib.outputs.html import fill_html
|
||||
from prowler.lib.outputs.json import fill_json_asff, fill_json_ocsf
|
||||
from prowler.lib.outputs.models import (
|
||||
Check_Output_JSON_ASFF,
|
||||
@@ -106,10 +105,6 @@ def report(check_findings, provider):
|
||||
file_descriptors["json-asff"].write(",")
|
||||
|
||||
# Common outputs
|
||||
if "html" in file_descriptors:
|
||||
fill_html(file_descriptors["html"], finding, output_options)
|
||||
file_descriptors["html"].write("")
|
||||
|
||||
if "csv" in file_descriptors:
|
||||
csv_writer, finding_output = generate_provider_output_csv(
|
||||
provider,
|
||||
|
||||
@@ -5,7 +5,6 @@ from tabulate import tabulate
|
||||
|
||||
from prowler.config.config import (
|
||||
csv_file_suffix,
|
||||
html_file_suffix,
|
||||
json_asff_file_suffix,
|
||||
json_file_suffix,
|
||||
json_ocsf_file_suffix,
|
||||
@@ -115,10 +114,6 @@ def display_summary_table(
|
||||
f"{Style.BRIGHT}* You only see here those services that contains resources.{Style.RESET_ALL}"
|
||||
)
|
||||
print("\nDetailed results are in:")
|
||||
if "html" in output_options.output_modes:
|
||||
print(
|
||||
f" - HTML: {output_directory}/{output_filename}{html_file_suffix}"
|
||||
)
|
||||
if "json-asff" in output_options.output_modes:
|
||||
print(
|
||||
f" - JSON-ASFF: {output_directory}/{output_filename}{json_asff_file_suffix}"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from prowler.config.config import (
|
||||
csv_file_suffix,
|
||||
html_file_suffix,
|
||||
json_asff_file_suffix,
|
||||
json_file_suffix,
|
||||
json_ocsf_file_suffix,
|
||||
@@ -22,8 +21,6 @@ def send_to_s3_bucket(
|
||||
filename = f"{output_filename}{json_asff_file_suffix}"
|
||||
elif output_mode == "json-ocsf":
|
||||
filename = f"{output_filename}{json_ocsf_file_suffix}"
|
||||
elif output_mode == "html":
|
||||
filename = f"{output_filename}{html_file_suffix}"
|
||||
else: # Compliance output mode
|
||||
filename = f"{output_filename}_{output_mode}{csv_file_suffix}"
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class Test_Parser:
|
||||
assert not parsed.quiet
|
||||
assert len(parsed.output_modes) == 4
|
||||
assert "csv" in parsed.output_modes
|
||||
assert "html" in parsed.output_modes
|
||||
|
||||
assert "json" in parsed.output_modes
|
||||
assert not parsed.output_filename
|
||||
assert "output" in parsed.output_directory
|
||||
@@ -92,7 +92,7 @@ class Test_Parser:
|
||||
assert not parsed.quiet
|
||||
assert len(parsed.output_modes) == 4
|
||||
assert "csv" in parsed.output_modes
|
||||
assert "html" in parsed.output_modes
|
||||
|
||||
assert "json" in parsed.output_modes
|
||||
assert not parsed.output_filename
|
||||
assert "output" in parsed.output_directory
|
||||
@@ -132,7 +132,7 @@ class Test_Parser:
|
||||
assert not parsed.quiet
|
||||
assert len(parsed.output_modes) == 4
|
||||
assert "csv" in parsed.output_modes
|
||||
assert "html" in parsed.output_modes
|
||||
|
||||
assert "json" in parsed.output_modes
|
||||
assert not parsed.output_filename
|
||||
assert "output" in parsed.output_directory
|
||||
@@ -167,7 +167,7 @@ class Test_Parser:
|
||||
assert not parsed.quiet
|
||||
assert len(parsed.output_modes) == 4
|
||||
assert "csv" in parsed.output_modes
|
||||
assert "html" in parsed.output_modes
|
||||
|
||||
assert "json" in parsed.output_modes
|
||||
assert not parsed.output_filename
|
||||
assert "output" in parsed.output_directory
|
||||
@@ -268,7 +268,6 @@ class Test_Parser:
|
||||
assert len(parsed.output_modes) == 4
|
||||
assert "csv" in parsed.output_modes
|
||||
assert "json" in parsed.output_modes
|
||||
assert "html" in parsed.output_modes
|
||||
|
||||
def test_root_parser_output_modes_short(self):
|
||||
command = [prowler_command, "-M", "csv"]
|
||||
|
||||
@@ -54,7 +54,6 @@ from prowler.lib.outputs.models import (
|
||||
Severity,
|
||||
generate_csv_fields,
|
||||
get_check_compliance,
|
||||
parse_html_string,
|
||||
parse_json_tags,
|
||||
unroll_dict,
|
||||
unroll_dict_to_list,
|
||||
@@ -301,49 +300,6 @@ class Test_Outputs:
|
||||
|
||||
assert unroll_dict_to_list(dict_B) == list_B
|
||||
|
||||
def test_parse_html_string(self):
|
||||
string = "CISA: your-systems-3, your-data-1, your-data-2 | CIS-1.4: 2.1.1 | CIS-1.5: 2.1.1 | GDPR: article_32 | AWS-Foundational-Security-Best-Practices: s3 | HIPAA: 164_308_a_1_ii_b, 164_308_a_4_ii_a, 164_312_a_2_iv, 164_312_c_1, 164_312_c_2, 164_312_e_2_ii | GxP-21-CFR-Part-11: 11.10-c, 11.30 | GxP-EU-Annex-11: 7.1-data-storage-damage-protection | NIST-800-171-Revision-2: 3_3_8, 3_5_10, 3_13_11, 3_13_16 | NIST-800-53-Revision-4: sc_28 | NIST-800-53-Revision-5: au_9_3, cm_6_a, cm_9_b, cp_9_d, cp_9_8, pm_11_b, sc_8_3, sc_8_4, sc_13_a, sc_16_1, sc_28_1, si_19_4 | ENS-RD2022: mp.si.2.aws.s3.1 | NIST-CSF-1.1: ds_1 | RBI-Cyber-Security-Framework: annex_i_1_3 | FFIEC: d3-pc-am-b-12 | PCI-3.2.1: s3 | FedRamp-Moderate-Revision-4: sc-13, sc-28 | FedRAMP-Low-Revision-4: sc-13"
|
||||
assert (
|
||||
parse_html_string(string)
|
||||
== """
|
||||
•CISA: your-systems-3, your-data-1, your-data-2
|
||||
|
||||
•CIS-1.4: 2.1.1
|
||||
|
||||
•CIS-1.5: 2.1.1
|
||||
|
||||
•GDPR: article_32
|
||||
|
||||
•AWS-Foundational-Security-Best-Practices: s3
|
||||
|
||||
•HIPAA: 164_308_a_1_ii_b, 164_308_a_4_ii_a, 164_312_a_2_iv, 164_312_c_1, 164_312_c_2, 164_312_e_2_ii
|
||||
|
||||
•GxP-21-CFR-Part-11: 11.10-c, 11.30
|
||||
|
||||
•GxP-EU-Annex-11: 7.1-data-storage-damage-protection
|
||||
|
||||
•NIST-800-171-Revision-2: 3_3_8, 3_5_10, 3_13_11, 3_13_16
|
||||
|
||||
•NIST-800-53-Revision-4: sc_28
|
||||
|
||||
•NIST-800-53-Revision-5: au_9_3, cm_6_a, cm_9_b, cp_9_d, cp_9_8, pm_11_b, sc_8_3, sc_8_4, sc_13_a, sc_16_1, sc_28_1, si_19_4
|
||||
|
||||
•ENS-RD2022: mp.si.2.aws.s3.1
|
||||
|
||||
•NIST-CSF-1.1: ds_1
|
||||
|
||||
•RBI-Cyber-Security-Framework: annex_i_1_3
|
||||
|
||||
•FFIEC: d3-pc-am-b-12
|
||||
|
||||
•PCI-3.2.1: s3
|
||||
|
||||
•FedRamp-Moderate-Revision-4: sc-13, sc-28
|
||||
|
||||
•FedRAMP-Low-Revision-4: sc-13
|
||||
"""
|
||||
)
|
||||
|
||||
def test_parse_json_tags(self):
|
||||
json_tags = [
|
||||
{"Key": "name", "Value": "test"},
|
||||
|
||||
@@ -4,7 +4,6 @@ from os import rmdir
|
||||
from boto3 import session
|
||||
from mock import patch
|
||||
|
||||
from prowler.lib.outputs.html import get_assessment_summary
|
||||
from prowler.providers.aws.lib.audit_info.audit_info import AWS_Audit_Info
|
||||
from prowler.providers.azure.lib.audit_info.audit_info import (
|
||||
Azure_Audit_Info,
|
||||
@@ -105,7 +104,7 @@ class Test_Common_Output_Options:
|
||||
# Set the arguments passed
|
||||
arguments = Namespace()
|
||||
arguments.quiet = True
|
||||
arguments.output_modes = ["html", "csv", "json"]
|
||||
arguments.output_modes = ["csv", "json"]
|
||||
arguments.output_directory = "output_test_directory"
|
||||
arguments.verbose = True
|
||||
arguments.output_filename = "output_test_filename"
|
||||
@@ -125,7 +124,7 @@ class Test_Common_Output_Options:
|
||||
assert output_options.security_hub_enabled
|
||||
assert output_options.send_sh_only_fails
|
||||
assert output_options.is_quiet
|
||||
assert output_options.output_modes == ["html", "csv", "json", "json-asff"]
|
||||
assert output_options.output_modes == ["csv", "json", "json-asff"]
|
||||
assert output_options.output_directory == arguments.output_directory
|
||||
assert output_options.mutelist_file == ""
|
||||
assert output_options.bulk_checks_metadata == {}
|
||||
@@ -141,7 +140,7 @@ class Test_Common_Output_Options:
|
||||
# Set the arguments passed
|
||||
arguments = Namespace()
|
||||
arguments.quiet = True
|
||||
arguments.output_modes = ["html", "csv", "json"]
|
||||
arguments.output_modes = ["csv", "json"]
|
||||
arguments.output_directory = "output_test_directory"
|
||||
arguments.verbose = True
|
||||
arguments.output_filename = "output_test_filename"
|
||||
@@ -156,7 +155,7 @@ class Test_Common_Output_Options:
|
||||
)
|
||||
assert isinstance(output_options, Gcp_Output_Options)
|
||||
assert output_options.is_quiet
|
||||
assert output_options.output_modes == ["html", "csv", "json"]
|
||||
assert output_options.output_modes == ["csv", "json"]
|
||||
assert output_options.output_directory == arguments.output_directory
|
||||
assert output_options.mutelist_file == ""
|
||||
assert output_options.bulk_checks_metadata == {}
|
||||
@@ -172,7 +171,7 @@ class Test_Common_Output_Options:
|
||||
# Set the arguments passed
|
||||
arguments = Namespace()
|
||||
arguments.quiet = True
|
||||
arguments.output_modes = ["html", "csv", "json"]
|
||||
arguments.output_modes = ["csv", "json"]
|
||||
arguments.output_directory = "output_test_directory"
|
||||
arguments.verbose = True
|
||||
arguments.output_filename = "output_test_filename"
|
||||
@@ -187,7 +186,7 @@ class Test_Common_Output_Options:
|
||||
)
|
||||
assert isinstance(output_options, Kubernetes_Output_Options)
|
||||
assert output_options.is_quiet
|
||||
assert output_options.output_modes == ["html", "csv", "json"]
|
||||
assert output_options.output_modes == ["csv", "json"]
|
||||
assert output_options.output_directory == arguments.output_directory
|
||||
assert output_options.mutelist_file == ""
|
||||
assert output_options.bulk_checks_metadata == {}
|
||||
@@ -203,7 +202,7 @@ class Test_Common_Output_Options:
|
||||
# Set the arguments passed
|
||||
arguments = Namespace()
|
||||
arguments.quiet = True
|
||||
arguments.output_modes = ["html", "csv", "json"]
|
||||
arguments.output_modes = ["csv", "json"]
|
||||
arguments.output_directory = "output_test_directory"
|
||||
arguments.verbose = True
|
||||
arguments.security_hub = True
|
||||
@@ -224,7 +223,7 @@ class Test_Common_Output_Options:
|
||||
assert output_options.security_hub_enabled
|
||||
assert output_options.send_sh_only_fails
|
||||
assert output_options.is_quiet
|
||||
assert output_options.output_modes == ["html", "csv", "json", "json-asff"]
|
||||
assert output_options.output_modes == ["csv", "json", "json-asff"]
|
||||
assert output_options.output_directory == arguments.output_directory
|
||||
assert output_options.mutelist_file == ""
|
||||
assert output_options.bulk_checks_metadata == {}
|
||||
@@ -243,7 +242,7 @@ class Test_Common_Output_Options:
|
||||
# Set the arguments passed
|
||||
arguments = Namespace()
|
||||
arguments.quiet = True
|
||||
arguments.output_modes = ["html", "csv", "json"]
|
||||
arguments.output_modes = ["csv", "json"]
|
||||
arguments.output_directory = "output_test_directory"
|
||||
arguments.verbose = True
|
||||
arguments.only_logs = False
|
||||
@@ -262,7 +261,6 @@ class Test_Common_Output_Options:
|
||||
assert isinstance(output_options, Azure_Output_Options)
|
||||
assert output_options.is_quiet
|
||||
assert output_options.output_modes == [
|
||||
"html",
|
||||
"csv",
|
||||
"json",
|
||||
]
|
||||
@@ -284,7 +282,7 @@ class Test_Common_Output_Options:
|
||||
# Set the arguments passed
|
||||
arguments = Namespace()
|
||||
arguments.quiet = True
|
||||
arguments.output_modes = ["html", "csv", "json"]
|
||||
arguments.output_modes = ["csv", "json"]
|
||||
arguments.output_directory = "output_test_directory"
|
||||
arguments.verbose = True
|
||||
arguments.only_logs = False
|
||||
@@ -304,7 +302,6 @@ class Test_Common_Output_Options:
|
||||
assert isinstance(output_options, Azure_Output_Options)
|
||||
assert output_options.is_quiet
|
||||
assert output_options.output_modes == [
|
||||
"html",
|
||||
"csv",
|
||||
"json",
|
||||
]
|
||||
@@ -320,177 +317,6 @@ class Test_Common_Output_Options:
|
||||
# Delete testing directory
|
||||
rmdir(arguments.output_directory)
|
||||
|
||||
def test_azure_get_assessment_summary(self):
|
||||
# Mock Azure Audit Info
|
||||
audit_info = self.set_mocked_azure_audit_info()
|
||||
tenants = ["tenant-1", "tenant-2"]
|
||||
audit_info.identity.tenant_ids = tenants
|
||||
audit_info.identity.subscriptions = {
|
||||
"Azure subscription 1": "12345-qwerty",
|
||||
"Subscription2": "12345-qwerty",
|
||||
}
|
||||
printed_subscriptions = []
|
||||
for key, value in audit_info.identity.subscriptions.items():
|
||||
intermediate = f"{key} : {value}"
|
||||
printed_subscriptions.append(intermediate)
|
||||
assert (
|
||||
get_assessment_summary(audit_info)
|
||||
== f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Azure Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Azure Tenant IDs:</b> {" ".join(audit_info.identity.tenant_ids)}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Tenant Domain:</b> {audit_info.identity.domain}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Subscriptions:</b> {" ".join(printed_subscriptions)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Azure Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Azure Identity Type:</b> {audit_info.identity.identity_type}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Identity ID:</b> {audit_info.identity.identity_id}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
def test_aws_get_assessment_summary(self):
|
||||
# Mock AWS Audit Info
|
||||
audit_info = self.set_mocked_aws_audit_info()
|
||||
|
||||
assert (
|
||||
get_assessment_summary(audit_info)
|
||||
== f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
AWS Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>AWS Account:</b> {audit_info.audited_account}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>AWS-CLI Profile:</b> default
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Audited Regions:</b> All Regions
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
AWS Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>User Id:</b> {audit_info.audited_user_id}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Caller Identity ARN:</b> {audit_info.audited_identity_arn}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
def test_gcp_get_assessment_summary(self):
|
||||
# Mock GCP Audit Info
|
||||
audit_info = self.set_mocked_gcp_audit_info()
|
||||
profile = "default"
|
||||
assert (
|
||||
get_assessment_summary(audit_info)
|
||||
== f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
GCP Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GCP Project IDs:</b> {', '.join(audit_info.project_ids)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
GCP Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GCP Account:</b> {profile}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
def test_kubernetes_get_assessment_summary(self):
|
||||
# Mock Kubernetes Audit Info
|
||||
audit_info = self.set_mocked_kubernetes_audit_info()
|
||||
assert (
|
||||
get_assessment_summary(audit_info)
|
||||
== """
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Kubernetes Assessment Summary
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes Context:</b> """
|
||||
+ audit_info.context["name"]
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Kubernetes Credentials
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes Cluster:</b> """
|
||||
+ audit_info.context["context"]["cluster"]
|
||||
+ """
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes User:</b> """
|
||||
+ audit_info.context["context"]["user"]
|
||||
+ """
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
|
||||
def test_get_provider_output_model(self):
|
||||
audit_info_class_names = [
|
||||
"AWS_Audit_Info",
|
||||
|
||||
Reference in New Issue
Block a user