diff --git a/backend/app.py b/backend/app.py index f941abf..e441761 100644 --- a/backend/app.py +++ b/backend/app.py @@ -5084,55 +5084,76 @@ def _build_findings_table(csv_path: str, max_rows: int = 10) -> str: def esc(v): return (v or "").replace("&","&").replace("<","<").replace(">",">") - def fmt(v): + def fmt(v, col=""): v = (v or "").strip() if not v: return "—" if v.startswith("=HYPERLINK"): m = _re.search(r',\s*"([^"]+)"', v) return m.group(1) if m else "—" if v.startswith("ocid1.") and len(v) > 30: - return "..." + v[-12:] - if len(v) > 50: - return v[:47] + "..." + return v[:25] + "..." + v[-8:] + # Format time fields as date + age in days + if col.lower() in ("time_created", "time_expires", "last_successful_login_date"): + try: + from datetime import datetime + dt = datetime.fromisoformat(v.replace("Z", "+00:00").split("+")[0].split(".")[0]) + age = (datetime.utcnow() - dt).days + return f"{v[:10]} ({age}d)" + except Exception: + return v[:16] + # Truncate long values but keep enough to identify + if len(v) > 60: + return v[:57] + "..." return v - # Priority columns: pick the most useful ones, max 4 columns - priority = ["name", "display_name", "username", "lifecycle_state", "status", - "is_mfa_activated", "domain_name", "description", "email", - "key_id", "age", "region", "cidr_block", "protocol", "port_range", - "visibility", "encryption", "versioning", "type", "severity"] + # Show all useful columns (skip only internal/redundant ones) all_cols = list(all_rows[0].keys()) - skip = {"extract_date", "deep_link", "domain_deeplink", "defined_tags", "compartment_id", - "domain_id", "external_identifier", "freeform_tags", "system_tags", "id", - "time_created", "email_verified", "is_federated", "groups", + skip = {"extract_date", "deep_link", "domain_deeplink", "defined_tags", + "domain_id", "external_identifier", "freeform_tags", "system_tags", + "email_verified", "is_federated", "api_keys", "auth_tokens", "customer_secret_keys", "database_passwords", "can_use_api_keys", "can_use_auth_tokens", "can_use_console_password", "can_use_customer_secret_keys", "can_use_db_credentials", - "can_use_o_auth2_client_credentials", "can_use_smtp_credentials", - "last_successful_login_date"} - avail = [c for c in all_cols if c.lower() not in skip] - # Pick up to 4 columns in priority order - display = [] - for p_col in priority: - for a_col in avail: - if a_col.lower() == p_col and a_col not in display: - display.append(a_col) - break - if len(display) >= 4: - break - if not display: - display = avail[:4] + "can_use_o_auth2_client_credentials", "can_use_smtp_credentials"} + display = [c for c in all_cols if c.lower() not in skip] + # Limit to max 7 columns to keep table readable + if len(display) > 7: + # Prioritize: identity cols first, then status, then details + priority_order = ["user_name", "name", "display_name", "username", "id", "user_id", + "key_id", "lifecycle_state", "status", "is_mfa_activated", + "domain_name", "email", "description", "fingerprint", + "time_created", "last_successful_login_date", "groups", + "compartment_id", "region", "cidr_block", "protocol", "port_range", + "visibility", "encryption", "versioning", "age", "time_expires", + "statements", "type", "severity"] + ordered = [] + for p in priority_order: + for d in display: + if d.lower() == p and d not in ordered: + ordered.append(d) + # Add any remaining not in priority + for d in display: + if d not in ordered: + ordered.append(d) + display = ordered[:7] # Pretty column names + _COL_LABELS = { + "user_name": "User", "user_id": "User ID", "key_id": "Key ID", + "fingerprint": "Fingerprint", "time_created": "Created (Age)", + "display_name": "Name", "lifecycle_state": "State", + "is_mfa_activated": "MFA", "domain_name": "Domain", + "cidr_block": "CIDR", "port_range": "Ports", + } def col_label(c): - return c.replace("_", " ").title() + return _COL_LABELS.get(c, c.replace("_", " ").title()) total = len(all_rows) show = all_rows[:max_rows] header = "".join(f'{esc(col_label(c))}' for c in display) rows_html = "" for row in show: - cells = "".join(f'{esc(fmt(row.get(c, "")))}' for c in display) + cells = "".join(f'{esc(fmt(row.get(c, ""), c))}' for c in display) rows_html += f"{cells}" note = f'
{total} non-compliant resource{"s" if total != 1 else ""}.' @@ -5175,13 +5196,15 @@ def _build_compliance_html(filtered_rows: list, tenancy_name: str, extract_date: sec = r["Section"] rec = r.get("Recommendation #", "") title = r.get("Title", "") + level = (r.get("Level", "") or "").strip() is_compliant = r["__compliant"] if sec != current_section: current_section = sec - summary_rows += f'{esc(sec)}' + summary_rows += f'{esc(sec)}' status_cls = "pass" if is_compliant else "fail" status_txt = "OK" if is_compliant else "FAILED" - summary_rows += f'{esc(rec)}{esc(title)}{status_txt}' + level_txt = f'L{level}' if level else '' + summary_rows += f'{esc(rec)}{esc(title)}{level_txt}{status_txt}' # ── Detailed sections ── sections_html = "" @@ -5194,6 +5217,7 @@ def _build_compliance_html(filtered_rows: list, tenancy_name: str, extract_date: for r in sd["items"]: rec = r.get("Recommendation #", "") title = r.get("Title", "") + level = (r.get("Level", "") or "").strip() rec_id = f"rec-{slug(sec_name)}-{slug(rec)}" is_compliant = r["__compliant"] findings = r.get("Findings", "").strip() @@ -5218,15 +5242,17 @@ def _build_compliance_html(filtered_rows: list, tenancy_name: str, extract_date: detail = f" ({findings_num} of {esc(total_items)} items)" result_html = f'
Result: Tenancy {esc(tenancy_name)}: Non-Compliant{detail}
' - # Description + # Description — preserve line breaks for readability description_html = "" if not is_compliant and rag_desc: - description_html = f'

Description:

{esc(rag_desc)}

' + desc_formatted = esc(rag_desc).replace('\n', '
') + description_html = f'

Description:

{desc_formatted}

' - # Rationale (why this matters) + # Rationale — preserve line breaks rationale_html = "" if not is_compliant and rag_rationale: - rationale_html = f'

Rationale:

{esc(rag_rationale)}

' + rat_formatted = esc(rag_rationale).replace('\n', '
') + rationale_html = f'

Rationale:

{rat_formatted}

' # Remediation — formatted with code snippets remediation_html = "" @@ -5257,9 +5283,14 @@ def _build_compliance_html(filtered_rows: list, tenancy_name: str, extract_date: if csv_path: findings_table_html = _build_findings_table(csv_path, max_rows=10) + level_badge = "" + if level: + lvl_cls = "level-1" if level == "1" else "level-2" + level_badge = f'Level {esc(level)}' + sections_html += f'''
-

{esc(rec)} {esc(title)}

+

{esc(rec)} {esc(title)} {level_badge}

{bar_html} {result_html} {findings_table_html} @@ -5283,8 +5314,11 @@ def _build_compliance_html(filtered_rows: list, tenancy_name: str, extract_date: rec_id = f"rec-{slug(sec_name)}-{slug(rec)}" toc_lines += f'' - PURPOSE_TEXT = "The purpose of the cloud security assessment is to comprehensively evaluate the organization's security posture in its cloud environment, identifying vulnerabilities and gaps that could compromise data integrity, confidentiality, and availability. This assessment aims to enhance information security maturity, strengthen defenses against cyber threats, ensure compliance with regulations, and foster a culture of security awareness. Through periodic assessments, the organization proactively mitigates risks, ensuring continuous security for cloud-configured assets and maintaining stakeholders' trust in data protection capabilities." - DISCLAIMER_TEXT = "This document in any form, software or printed matter, contains proprietary information that is the exclusive property of Oracle. Your access to and use of this confidential material is subject to the terms and conditions of your Oracle software license and service agreement, which has been executed and with which you agree to comply. This document and information contained herein may not be disclosed, copied, reproduced or distributed to anyone outside Oracle without prior written consent of Oracle. This document is not part of your license agreement, nor can it be incorporated into any contractual agreement with Oracle or its subsidiaries or affiliates.\n\nThis document is for informational purposes only and is intended solely to assist you in planning for the implementation and upgrade of the product features described. It is not a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions." + DISCLAIMER_TEXT = "This document provides prescriptive guidance for establishing a secure baseline configuration for the Oracle Cloud Infrastructure environment based on the CIS Oracle Cloud Infrastructure Foundations Benchmark. The scope of this benchmark is to establish a base level of security for anyone utilizing the included Oracle Cloud Infrastructure services. The benchmark is, however, not an exhaustive list of all possible security configurations and architectures. You should take the benchmark as a starting point and do the required site-specific tailoring wherever needed and when it is prudent to do so.\n\nThis document is provided \"as is\" for informational purposes only, without warranties of any kind, whether express or implied. The findings and recommendations reflect the conditions observed at the time of the assessment and do not guarantee the prevention of future security incidents or compliance outcomes. This document does not form part of any Oracle license, service contract, or other agreement." + + PROFILE_DEFINITIONS = """Level 1 — Items in this profile intend to be practical and prudent, provide a clear security benefit, and not inhibit the utility of the technology beyond acceptable means. + +Level 2 — Items in this profile extend the Level 1 profile. Items in this profile exhibit one or more of the following characteristics: are intended for environments or use cases where security is paramount, act as defense in depth measure, and may negatively inhibit the utility or performance of the technology.""" SECURITY_OVERVIEW = """Oracle's mission is to build cloud infrastructure and platform services for your business to have effective and manageable security to run your mission-critical workloads and store your data with confidence. Oracle Cloud Infrastructure's security approach is based on seven core pillars: Customer Isolation — Allow customers to deploy their application and data assets in an environment that commits full isolation from other tenants and Oracle's staff. @@ -5305,7 +5339,7 @@ def _build_compliance_html(filtered_rows: list, tenancy_name: str, extract_date: -LAD A-Team CIS Compliance Report — {esc(tenancy_name)} +OCI CIS Foundations Benchmark Assessment — {esc(tenancy_name)}