"""DOCX compliance report generation — camo strip, floating images, professional formatting.""" import os, json, re, io from pathlib import Path from datetime import datetime from typing import Optional, List, Dict, Any from config import REPORTS, log from database import db from services.compliance_gen import _OCI_SERVICE_DESCRIPTIONS, _check_oci_services, _generate_compliance_report def _generate_camo_strip() -> bytes: """Generate a camouflage-style decorative strip PNG (like Oracle PDF reports).""" from PIL import Image, ImageDraw, ImageFilter import random, io as _io W, H = 36, 1500 # Exact colors from the HTML page-strip gradient — olive/sage/gold tones C1 = (107, 127, 58) # #6b7f3a olive green C2 = (138, 154, 78) # #8a9a4e sage green C3 = (74, 103, 65) # #4a6741 forest C4 = (45, 59, 42) # #2d3b2a dark forest C5 = (196, 169, 77) # #c4a94d gold C6 = (139, 109, 59) # #8b6d3b brown/tan C7 = (61, 92, 58) # #3d5c3a deep green ALL = [C1, C2, C3, C4, C5, C6, C7] img = Image.new('RGB', (W, H), C4) draw = ImageDraw.Draw(img) random.seed(42) import math def _poly(cx, cy, r, sides=5): pts = [] for i in range(sides): a = 2 * math.pi * i / sides + random.uniform(-0.5, 0.5) rx = r * random.uniform(0.5, 1.4) ry = r * random.uniform(0.8, 2.2) pts.append((cx + rx * math.cos(a), cy + ry * math.sin(a))) return pts # Vertical gradient base (like the CSS linear-gradient) for y in range(H): t = y / H # Cycle through gradient stops if t < 0.16: c = C1 if t < 0.08 else C2 elif t < 0.32: c = C3 if t < 0.24 else C4 elif t < 0.48: c = C5 if t < 0.40 else C6 elif t < 0.64: c = C7 if t < 0.56 else C1 elif t < 0.80: c = C5 if t < 0.72 else C3 else: c = C4 if t < 0.88 else C2 draw.line([(0, y), (W, y)], fill=c) # Organic patches on top for camo texture for _ in range(35): c = random.choice(ALL) cx = random.randint(-8, W + 8) cy = random.randint(-20, H + 20) draw.polygon(_poly(cx, cy, random.randint(12, 30), random.randint(4, 6)), fill=c) for _ in range(25): c = random.choice([C5, C6, C2]) cx = random.randint(0, W) cy = random.randint(0, H) draw.polygon(_poly(cx, cy, random.randint(6, 16), random.randint(3, 5)), fill=c) img = img.filter(ImageFilter.GaussianBlur(radius=1.2)) buf = _io.BytesIO() img.save(buf, format='PNG') return buf.getvalue() def _add_floating_image_to_header(header, image_bytes: bytes, width_emu: int, height_emu: int): """Add a floating behind-text image to a DOCX header (positioned at left edge, full page height).""" from docx.oxml.ns import nsdecls from docx.oxml import parse_xml, OxmlElement import io as _io rId, _ = header.part.get_or_add_image(_io.BytesIO(image_bytes)) sw, sh = str(width_emu), str(height_emu) anchor_xml = f''' 0 0 ''' anchor_el = parse_xml(anchor_xml) drawing = OxmlElement('w:drawing') drawing.append(anchor_el) # Add to the first run in header paragraph p = header.paragraphs[0] runs = p.runs if runs: runs[0]._r.append(drawing) else: r = p.add_run() r._r.append(drawing) def _generate_compliance_docx(rid: str, tenancy: str, file_map: dict) -> bytes: """Generate a professionally styled DOCX version of the compliance report matching the HTML/PDF.""" import csv as csvmod, io as _io, re as _re from datetime import datetime as _dt from docx import Document from docx.shared import Inches, Pt, Cm, RGBColor, Emu from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_TABLE_ALIGNMENT from docx.oxml.ns import qn, nsdecls from docx.oxml import parse_xml, OxmlElement import json as _json rdir = REPORTS / rid # Read the same processed data used by the HTML report (ensures identical content) data_path = rdir / "compliance_data.json" if not data_path.exists(): log.warning(f"DOCX: compliance_data.json not found for {rid} — generate HTML report first") return None with open(data_path, "r", encoding="utf-8") as f: report_data = _json.load(f) tenancy_name = report_data.get("tenancy_name", tenancy) extract_date = report_data.get("extract_date", "") filtered = report_data.get("filtered", []) sections_data = report_data.get("sections", {}) oci_services = report_data.get("oci_services", []) file_path_map = report_data.get("file_path_map", file_map) if not filtered: return None now = _dt.utcnow() months_en = ["January","February","March","April","May","June","July","August","September","October","November","December"] month_year = f"{months_en[now.month - 1]}, {now.year}" # ── Exact colors from HTML/PDF spec ── ORACLE_RED = RGBColor(0xC7, 0x46, 0x34) HEADER_GREEN = '4A7C59' FINDINGS_HEADER_GREEN = '4A6741' ROW_ALT = 'EEF3F0' GREEN = RGBColor(0x0F, 0x7B, 0x0F) RED = RGBColor(0xC0, 0x00, 0x00) MUTED = RGBColor(0x64, 0x74, 0x8B) WHITE = RGBColor(0xFF, 0xFF, 0xFF) BODY_TEXT = RGBColor(0x1A, 0x1A, 0x1A) TITLE_DARK = RGBColor(0x11, 0x18, 0x27) SUBTITLE_GRAY = RGBColor(0x37, 0x41, 0x51) ORANGE = RGBColor(0xD9, 0x77, 0x06) # ── Helpers ── def _cell_shading(cell, color_hex): shading = parse_xml(f'') cell._tc.get_or_add_tcPr().append(shading) def _set_cell_width(cell, width_inches): tcPr = cell._tc.get_or_add_tcPr() tcW = OxmlElement('w:tcW') tcW.set(qn('w:w'), str(int(width_inches * 1440))) tcW.set(qn('w:type'), 'dxa') tcPr.append(tcW) def _cell_margins(cell, top=40, bottom=40, left=80, right=80): """Set cell margins in twips for tighter padding.""" tcPr = cell._tc.get_or_add_tcPr() tcMar = OxmlElement('w:tcMar') for side, val in [('top', top), ('bottom', bottom), ('start', left), ('end', right)]: el = OxmlElement(f'w:{side}') el.set(qn('w:w'), str(val)) el.set(qn('w:type'), 'dxa') tcMar.append(el) tcPr.append(tcMar) def _cell_text(cell, text, bold=False, color=None, size=Pt(9), align=WD_ALIGN_PARAGRAPH.LEFT, uppercase=False): cell.text = "" p = cell.paragraphs[0] p.alignment = align p.paragraph_format.space_before = Pt(2) p.paragraph_format.space_after = Pt(2) p.paragraph_format.line_spacing = 1.0 display = str(text).upper() if uppercase else str(text) r = p.add_run(display) r.font.size = size r.font.name = 'Calibri' r.bold = bold if color: r.font.color.rgb = color else: r.font.color.rgb = BODY_TEXT def _table_borders(table, color='D0D5DD', size='4'): tbl = table._tbl tblPr = tbl.tblPr if tbl.tblPr is not None else OxmlElement('w:tblPr') borders = OxmlElement('w:tblBorders') for edge in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'): el = OxmlElement(f'w:{edge}') el.set(qn('w:val'), 'single') el.set(qn('w:sz'), size) el.set(qn('w:space'), '0') el.set(qn('w:color'), color) borders.append(el) tblPr.append(borders) def _set_repeat_header(table): """Make first row repeat as header on page breaks (like PDF).""" tr = table.rows[0]._tr trPr = tr.get_or_add_trPr() tblHeader = OxmlElement('w:tblHeader') trPr.append(tblHeader) def _no_cell_spacing(table): """Remove cell spacing for tight table look.""" tbl = table._tbl tblPr = tbl.tblPr if tbl.tblPr is not None else OxmlElement('w:tblPr') spacing = OxmlElement('w:tblCellSpacing') spacing.set(qn('w:w'), '0') spacing.set(qn('w:type'), 'dxa') tblPr.append(spacing) def _styled_table(doc, headers, data_rows, col_widths=None, header_align=None, header_color=None): """Create a table matching the PDF style: green header, alternating rows.""" hdr_clr = header_color or HEADER_GREEN tbl = doc.add_table(rows=1 + len(data_rows), cols=len(headers)) tbl.autofit = False tbl.allow_autofit = False tbl.alignment = WD_TABLE_ALIGNMENT.CENTER # Set column widths if provided if col_widths: for i, w in enumerate(col_widths): for row in tbl.rows: _set_cell_width(row.cells[i], w) # Header row — green background, white text, uppercase for i, h in enumerate(headers): cell = tbl.rows[0].cells[i] _cell_shading(cell, hdr_clr) _cell_margins(cell) h_align = WD_ALIGN_PARAGRAPH.CENTER if header_align and i < len(header_align): h_align = header_align[i] elif i == 0: h_align = WD_ALIGN_PARAGRAPH.LEFT _cell_text(cell, h, bold=True, color=WHITE, size=Pt(9), align=h_align, uppercase=True) # Data rows — alternating green tint / white for ridx, row_data in enumerate(data_rows): bg = ROW_ALT if ridx % 2 == 0 else 'FFFFFF' for cidx, val in enumerate(row_data): cell = tbl.rows[ridx + 1].cells[cidx] _cell_shading(cell, bg) _cell_margins(cell) if isinstance(val, tuple): txt, bld, clr = val[0], val[1], val[2] a = WD_ALIGN_PARAGRAPH.CENTER if cidx > 0 and len(str(txt)) < 10 else WD_ALIGN_PARAGRAPH.LEFT _cell_text(cell, txt, bold=bld, color=clr, size=Pt(9), align=a) else: a = WD_ALIGN_PARAGRAPH.CENTER if cidx > 0 and len(str(val)) < 10 else WD_ALIGN_PARAGRAPH.LEFT _cell_text(cell, val, size=Pt(9), align=a) _table_borders(tbl) _no_cell_spacing(tbl) _set_repeat_header(tbl) return tbl def _add_heading_red(doc, text, level=1): h = doc.add_heading(text, level=level) for r in h.runs: r.font.color.rgb = ORACLE_RED r.font.name = 'Calibri' return h def _clean_text(text): """Join artificially broken lines into flowing paragraphs. Preserves: paragraph breaks, numbered lists, bullets, headers.""" if not text: return text lines = text.split('\n') result = [] for line in lines: line = line.strip() if not line: result.append('\n') continue starts_new = ( _re.match(r'^\d+[\.\)]\s', line) or _re.match(r'^[a-z][\.\)]\s', line) or line.startswith('- ') or line.startswith('* ') or line.startswith('From ') or line.startswith('Note:') or line.startswith('See ') ) if starts_new: result.append(line) elif result and result[-1] != '\n' and not result[-1].endswith(('.', ':', '!', '?', ';')): result[-1] = result[-1] + ' ' + line else: result.append(line) return '\n'.join(result).strip() def _add_body_para(doc, text, size=Pt(10), color=None, bold=False, italic=False, space_after=Pt(8), space_before=Pt(0), align=WD_ALIGN_PARAGRAPH.JUSTIFY): """Add paragraph(s) with consistent body styling. Detects numbered/bullet lists and indents them.""" text = _clean_text(text) if not text: return None last_p = None for chunk in text.split('\n'): chunk = chunk.strip() if not chunk: continue # Detect list items: "1. text", "a. text", "- text", "* text" list_match = _re.match(r'^(\d+[\.\)]|[a-z][\.\)]|-|\*)\s+(.+)', chunk) p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.LEFT if list_match else align p.paragraph_format.space_before = Pt(1) if list_match else space_before p.paragraph_format.space_after = Pt(3) if list_match else space_after p.paragraph_format.line_spacing = 1.25 if list_match: # Indent list items p.paragraph_format.left_indent = Inches(0.3) p.paragraph_format.first_line_indent = Inches(-0.3) # Number/bullet in bold r = p.add_run(list_match.group(1) + ' ') r.font.name = 'Calibri' r.font.size = size r.font.color.rgb = RGBColor(0x2F, 0x55, 0x97) r.bold = True # Content r = p.add_run(list_match.group(2)) r.font.name = 'Calibri' r.font.size = size r.font.color.rgb = color or BODY_TEXT else: r = p.add_run(chunk) r.font.name = 'Calibri' r.font.size = size r.font.color.rgb = color or BODY_TEXT r.bold = bold r.italic = italic last_p = p return last_p def _is_code_line(line): """Detect if a line is a CLI command or code snippet.""" line = line.strip() if not line: return False # Strong indicators: starts with known CLI commands cli_starts = ['oci ', 'aws ', 'az ', 'gcloud ', 'terraform ', 'kubectl ', 'curl ', 'grep ', 'cat ', 'echo ', 'export ', 'sudo ', '#!/'] if any(line.startswith(p) for p in cli_starts): return True # Continuation lines (start with --flag or -flag=value) if _re.match(r'^--\w+', line) or _re.match(r'^-\w+=', line): return True # Shell constructs if line in ('do', 'done', 'fi', 'then', 'else', 'esac'): return True if line.startswith('for ') and (' in ' in line or line.endswith('do')): return True return False def _is_section_header(line): """Detect "From Console:", "From Command Line:", "Audit / Verification:", etc.""" low = line.strip().lower() return (low.startswith('from console') or low.startswith('from oci console') or low.startswith('from command') or low.startswith('from cli') or low.startswith('from the command') or low.startswith('audit') and 'verification' in low) def _add_code_block(doc, code_text): """Add a code block with monospace font and gray background.""" p = doc.add_paragraph() p.paragraph_format.space_before = Pt(6) p.paragraph_format.space_after = Pt(8) p.paragraph_format.line_spacing = 1.1 p.alignment = WD_ALIGN_PARAGRAPH.LEFT pPr = p._p.get_or_add_pPr() pShd = OxmlElement('w:shd') pShd.set(qn('w:val'), 'clear') pShd.set(qn('w:color'), 'auto') pShd.set(qn('w:fill'), 'F3F4F6') pPr.append(pShd) pBdr = OxmlElement('w:pBdr') for edge_name, color in [('left', '9CA3AF'), ('top', 'E5E7EB'), ('bottom', 'E5E7EB'), ('right', 'E5E7EB')]: edge = OxmlElement(f'w:{edge_name}') edge.set(qn('w:val'), 'single') edge.set(qn('w:sz'), '6' if edge_name == 'left' else '4') edge.set(qn('w:space'), '6' if edge_name == 'left' else '2') edge.set(qn('w:color'), color) pBdr.append(edge) pPr.append(pBdr) r = p.add_run(code_text) r.font.name = 'Consolas' r.font.size = Pt(8) r.font.color.rgb = RGBColor(0x1F, 0x29, 0x37) def _add_remediation_docx(doc, remediation_text): """Add remediation text with code blocks and section headers, matching HTML output.""" # Split into paragraphs first (double newline), then process each paragraphs = _re.split(r'\n{2,}', remediation_text) for para_text in paragraphs: para_text = para_text.strip() if not para_text: continue lines = para_text.split('\n') # Check if entire paragraph is code non_empty = [l.strip() for l in lines if l.strip()] if non_empty and all(_is_code_line(l) for l in non_empty): _add_code_block(doc, '\n'.join(non_empty)) continue # Mixed content: process line by line code_buffer = [] text_buffer = [] def flush_code(): if code_buffer: _add_code_block(doc, '\n'.join(code_buffer)) code_buffer.clear() def flush_text(): if text_buffer: joined = ' '.join(text_buffer) _add_body_para(doc, joined, size=Pt(9), space_after=Pt(6)) text_buffer.clear() for line in lines: stripped = line.strip() if not stripped: continue if _is_section_header(stripped): flush_text() flush_code() p = doc.add_paragraph() p.paragraph_format.space_before = Pt(8) p.paragraph_format.space_after = Pt(4) r = p.add_run(stripped) r.bold = True r.font.size = Pt(10) r.font.name = 'Calibri' r.font.color.rgb = BODY_TEXT elif _is_code_line(stripped): flush_text() code_buffer.append(stripped) else: flush_code() # If line ends with sentence punctuation, flush as separate paragraph text_buffer.append(stripped) if stripped.endswith(('.', ':', '!', '?', ';')): flush_text() flush_code() flush_text() def _result_box(doc, tenancy_name, is_compliant, findings="0", total="0"): """Add a result box with colored background and left border (matching PDF).""" p = doc.add_paragraph() pPr = p._p.get_or_add_pPr() # Background shading pShd = OxmlElement('w:shd') pShd.set(qn('w:val'), 'clear') pShd.set(qn('w:color'), 'auto') pShd.set(qn('w:fill'), 'F0FDF4' if is_compliant else 'FEF2F2') pPr.append(pShd) # Left border pBdr = OxmlElement('w:pBdr') left_b = OxmlElement('w:left') left_b.set(qn('w:val'), 'single') left_b.set(qn('w:sz'), '18') left_b.set(qn('w:space'), '6') left_b.set(qn('w:color'), '0F7B0F' if is_compliant else 'C00000') pBdr.append(left_b) pPr.append(pBdr) # Padding via indentation pInd = OxmlElement('w:ind') pInd.set(qn('w:left'), '120') pPr.append(pInd) p.paragraph_format.space_before = Pt(8) p.paragraph_format.space_after = Pt(8) run = p.add_run("Result: ") run.bold = True run.font.size = Pt(10) run.font.color.rgb = BODY_TEXT run.font.name = 'Calibri' run = p.add_run(f"Tenancy {tenancy_name}: ") run.bold = True run.font.size = Pt(10) run.font.color.rgb = BODY_TEXT run.font.name = 'Calibri' if is_compliant: run = p.add_run("Compliant") run.font.color.rgb = GREEN run.bold = True run.font.size = Pt(10) run.font.name = 'Calibri' else: detail = f" ({findings} of {total} items)" if findings and total else "" run = p.add_run(f"Non-Compliant{detail}") run.font.color.rgb = RED run.bold = True run.font.size = Pt(10) run.font.name = 'Calibri' return p doc = Document() # ── Global styles ── style = doc.styles['Normal'] style.font.name = 'Calibri' style.font.size = Pt(10) style.font.color.rgb = BODY_TEXT style.paragraph_format.space_after = Pt(4) style.paragraph_format.line_spacing = 1.15 for i in range(1, 4): hs = doc.styles[f'Heading {i}'] hs.font.color.rgb = ORACLE_RED hs.font.name = 'Calibri' doc.styles['Heading 1'].font.size = Pt(22) doc.styles['Heading 1'].font.bold = True doc.styles['Heading 2'].font.size = Pt(16) doc.styles['Heading 3'].font.size = Pt(12) # ── Page size A4 + Margins ── sect = doc.sections[0] sect.page_width = Cm(21.0) sect.page_height = Cm(29.7) sect.top_margin = Cm(2.5) sect.bottom_margin = Cm(2.0) sect.left_margin = Cm(2.5) sect.right_margin = Cm(2.5) # ── Header / Footer ── hdr = sect.header hdr.is_linked_to_previous = False hp = hdr.paragraphs[0] hp.alignment = WD_ALIGN_PARAGRAPH.LEFT r = hp.add_run("ORACLE") r.font.color.rgb = ORACLE_RED r.font.size = Pt(14) r.bold = True r.font.name = 'Calibri' # Add decorative camouflage strip to header (floating, behind text, left edge) try: camo_bytes = _generate_camo_strip() strip_w = int(1.0 * 360000) # ~1cm in EMU strip_h = int(29.7 * 360000) # A4 height (297mm) in EMU _add_floating_image_to_header(hdr, camo_bytes, strip_w, strip_h) except Exception as e: log.warning(f"DOCX: failed to add camo strip: {e}") ftr = sect.footer ftr.is_linked_to_previous = False fp = ftr.paragraphs[0] fp.alignment = WD_ALIGN_PARAGRAPH.CENTER r = fp.add_run("Confidential \u2013 Oracle Restricted") r.font.size = Pt(8) r.font.color.rgb = MUTED r.font.name = 'Calibri' # ═══════════════════════════════════════════════ # ── COVER PAGE ── # ═══════════════════════════════════════════════ # Spacer to push title to center of page for _ in range(10): sp = doc.add_paragraph() sp.paragraph_format.space_before = Pt(0) sp.paragraph_format.space_after = Pt(0) sp.paragraph_format.line_spacing = 1.0 # Title — centered, serif font p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_before = Pt(0) p.paragraph_format.space_after = Pt(0) r = p.add_run("CIS Foundations") r.font.size = Pt(36) r.font.color.rgb = TITLE_DARK r.font.name = 'Georgia' r.bold = False p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_before = Pt(0) p.paragraph_format.space_after = Pt(12) r = p.add_run("Benchmark Assessment") r.font.size = Pt(36) r.font.color.rgb = TITLE_DARK r.font.name = 'Georgia' r.bold = False # Subtitle — centered p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_before = Pt(8) p.paragraph_format.space_after = Pt(8) r = p.add_run(f"{tenancy} \u2013 Oracle Cloud Infrastructure") r.font.size = Pt(13) r.font.color.rgb = SUBTITLE_GRAY r.font.name = 'Calibri' # Extra spacer before metadata for _ in range(6): sp = doc.add_paragraph() sp.paragraph_format.space_before = Pt(0) sp.paragraph_format.space_after = Pt(0) sp.paragraph_format.line_spacing = 1.0 # Metadata — identical to PDF cover def _meta_line(doc, text, bold_prefix=None, sz=Pt(10)): p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.paragraph_format.space_before = Pt(1) p.paragraph_format.space_after = Pt(1) p.paragraph_format.line_spacing = 1.3 if bold_prefix: r = p.add_run(f"{bold_prefix} ") r.bold = True r.font.size = sz r.font.color.rgb = BODY_TEXT r.font.name = 'Calibri' r = p.add_run(text) else: r = p.add_run(text) r.font.size = sz r.font.color.rgb = SUBTITLE_GRAY r.font.name = 'Calibri' _meta_line(doc, tenancy, bold_prefix="Tenancy:") _meta_line(doc, f"{month_year}, Version [1.0]", sz=Pt(9)) if extract_date: _meta_line(doc, extract_date, bold_prefix="Extract date:", sz=Pt(9)) _meta_line(doc, f"Copyright \u00A9 {now.year}, Oracle and/or its affiliates", sz=Pt(9)) _meta_line(doc, "Confidential \u2013 Oracle Restricted", sz=Pt(9)) doc.add_page_break() # ═══════════════════════════════════════════════ # ── DISCLAIMER ── # ═══════════════════════════════════════════════ _add_heading_red(doc, "Disclaimer", level=1) _add_body_para(doc, '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.', space_after=Pt(8)) _add_body_para(doc, 'This 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.', space_after=Pt(8)) # ═══════════════════════════════════════════════ # ── PROFILE DEFINITIONS ── # ═══════════════════════════════════════════════ _add_heading_red(doc, "Profile Definitions", level=1) p = doc.add_paragraph() p.paragraph_format.line_spacing = 1.15 r = p.add_run("Level 1") r.bold = True r.font.name = 'Calibri' r.font.color.rgb = BODY_TEXT r = p.add_run(" \u2014 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.") r.font.name = 'Calibri' r.font.color.rgb = BODY_TEXT p = doc.add_paragraph() p.paragraph_format.line_spacing = 1.15 r = p.add_run("Level 2") r.bold = True r.font.name = 'Calibri' r.font.color.rgb = BODY_TEXT r = p.add_run(" \u2014 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.") r.font.name = 'Calibri' r.font.color.rgb = BODY_TEXT # ═══════════════════════════════════════════════ # ── TABLE OF CONTENTS ── # ═══════════════════════════════════════════════ doc.add_page_break() _add_heading_red(doc, "Table of Contents", level=1) p = doc.add_paragraph() r = p.add_run() fld1 = OxmlElement('w:fldChar'); fld1.set(qn('w:fldCharType'), 'begin'); r._r.append(fld1) inst = OxmlElement('w:instrText'); inst.set(qn('xml:space'), 'preserve'); inst.text = ' TOC \\o "1-3" \\h \\z \\u '; r._r.append(inst) fld2 = OxmlElement('w:fldChar'); fld2.set(qn('w:fldCharType'), 'separate'); r._r.append(fld2) ph = OxmlElement('w:r'); pht = OxmlElement('w:t'); pht.text = "Right-click and select 'Update Field' to generate TOC"; ph.append(pht); r._r.append(ph) fld3 = OxmlElement('w:fldChar'); fld3.set(qn('w:fldCharType'), 'end'); r._r.append(fld3) # Auto-update fields on open settings = doc.settings.element uf = OxmlElement('w:updateFields'); uf.set(qn('w:val'), 'true'); settings.append(uf) doc.add_page_break() # ═══════════════════════════════════════════════ # ── SECURITY OVERVIEW ── # ═══════════════════════════════════════════════ _add_heading_red(doc, "Security Overview", level=1) _add_body_para(doc, 'Oracle Cloud Infrastructure (OCI) is designed with security as a foundational pillar. ' 'The CIS Oracle Cloud Infrastructure Foundations Benchmark provides prescriptive guidance for ' 'establishing a secure baseline configuration. This assessment evaluates the tenancy against the ' 'benchmark controls across key security domains including Identity and Access Management, Networking, ' 'Logging and Monitoring, Object Storage, and other critical infrastructure services.', space_after=Pt(8)) _add_body_para(doc, 'The following sections present a summary of findings by domain, a detailed benchmark recommendation ' 'summary, and individual findings with remediation guidance for non-compliant controls.', space_after=Pt(8)) doc.add_page_break() # ═══════════════════════════════════════════════ # ── DOMAINS SUMMARY TABLE (data from compliance_data.json — identical to HTML) ── # ═══════════════════════════════════════════════ _add_heading_red(doc, "CIS Security Assessment", level=1) _add_body_para(doc, f"Number of controls per domain and non-compliant controls on tenancy {tenancy}:", space_after=Pt(8), align=WD_ALIGN_PARAGRAPH.LEFT) domain_data = [ (sec, str(sd["total"]), (str(sd["failed"]), True, RED), (str(sd["passed"]), True, GREEN)) for sec, sd in sections_data.items() ] _styled_table(doc, ["DOMAINS", "TOTAL CONTROLS", "FAILED", "PASSED"], domain_data, col_widths=[3.5, 1.2, 0.9, 0.9]) # Totals summary line total_all = sum(sd["total"] for sd in sections_data.values()) total_passed = sum(sd["passed"] for sd in sections_data.values()) total_failed = total_all - total_passed pct = round(total_passed / total_all * 100) if total_all else 0 p = doc.add_paragraph() p.paragraph_format.space_before = Pt(10) p.paragraph_format.space_after = Pt(4) r = p.add_run(f"Total: {total_all} controls | ") r.font.size = Pt(10) r.font.color.rgb = BODY_TEXT r.font.name = 'Calibri' r = p.add_run(f"Passed: {total_passed} ({pct}%)") r.font.color.rgb = GREEN r.font.size = Pt(10) r.bold = True r.font.name = 'Calibri' r = p.add_run(" | ") r.font.size = Pt(10) r.font.color.rgb = BODY_TEXT r.font.name = 'Calibri' r = p.add_run(f"Failed: {total_failed}") r.font.color.rgb = RED r.font.size = Pt(10) r.bold = True r.font.name = 'Calibri' doc.add_page_break() # ═══════════════════════════════════════════════ # ── BENCHMARK SUMMARY TABLE ── # ═══════════════════════════════════════════════ _add_heading_red(doc, "CIS Benchmark Recommendation Summary", level=1) bench_data = [] for r in filtered: rec = r.get("Recommendation #", "") title_txt = r.get("Title", "") level = r.get("Level", "") compliant = r.get("__compliant", False) st_text = "OK" if compliant else "FAILED" st_color = GREEN if compliant else RED bench_data.append((rec, title_txt, f"L{level}" if level else "", (st_text, True, st_color))) _styled_table(doc, ["Item", "Description", "Level", "Status"], bench_data, col_widths=[0.6, 4.0, 0.5, 0.8]) doc.add_page_break() # ═══════════════════════════════════════════════ # ── DETAILED FINDINGS ── # ═══════════════════════════════════════════════ for sec_name, sd in sections_data.items(): _add_heading_red(doc, sec_name, level=1) for r in sd["items"]: compliant = r.get("__compliant", False) rec = r.get("Recommendation #", "") title_txt = r.get("Title", "") level = r.get("Level", "") findings = r.get("Findings", "0") total = r.get("Total", "0") csv_remediation = (r.get("Remediation", "") or "").strip() rag_data = r.get("__rag_remediation", {}) if isinstance(rag_data, str): rag_data = {"description": "", "rationale": "", "remediation": rag_data} rag_desc = (rag_data.get("description", "") or "").strip() if isinstance(rag_data, dict) else "" rag_rationale = (rag_data.get("rationale", "") or "").strip() if isinstance(rag_data, dict) else "" rag_rem = (rag_data.get("remediation", "") or "").strip() if isinstance(rag_data, dict) else "" # Recommendation heading in Oracle Red _add_heading_red(doc, f"{rec} {title_txt}", level=2) # Level badge if level: p = doc.add_paragraph() p.paragraph_format.space_before = Pt(0) p.paragraph_format.space_after = Pt(4) r_run = p.add_run(f" Level {level} ") r_run.font.size = Pt(8) r_run.bold = True r_run.font.color.rgb = MUTED r_run.font.name = 'Calibri' rPr = r_run._r.get_or_add_rPr() shd = OxmlElement('w:shd') shd.set(qn('w:val'), 'clear') shd.set(qn('w:color'), 'auto') shd.set(qn('w:fill'), 'E8E8E8') rPr.append(shd) # Result box with colored background and left border _result_box(doc, tenancy_name, compliant, findings, total) # Description (from RAG — same as HTML) if not compliant and rag_desc: _add_heading_red(doc, "Description:", level=3) _add_body_para(doc, rag_desc, size=Pt(9), space_after=Pt(4)) # Rationale (from RAG — same as HTML) if not compliant and rag_rationale: _add_heading_red(doc, "Rationale:", level=3) _add_body_para(doc, rag_rationale, size=Pt(9), space_after=Pt(4)) # Remediation (from RAG or CSV — same as HTML, with code blocks) remediation = rag_rem or csv_remediation if not compliant and remediation: _add_heading_red(doc, "Remediation:", level=3) _add_remediation_docx(doc, remediation) # Affected Resources table if not compliant: csv_filename = (r.get("Filename", "") or "").strip().strip('"').strip("'") if csv_filename and csv_filename in file_path_map: csv_file_path = Path(file_path_map[csv_filename]) if csv_file_path.exists(): try: with open(csv_file_path, "r", encoding="utf-8") as cf: csv_rows = list(csvmod.DictReader(cf)) if csv_rows: cols = [c for c in csv_rows[0].keys() if c.lower() not in ("extract_date", "deep_link", "domain_deeplink", "defined_tags", "domain_id", "external_identifier", "freeform_tags", "system_tags")][:5] res_data = [] for crow in csv_rows[:10]: row_vals = [] for c in cols: val = crow.get(c, "") if val.startswith("=HYPERLINK"): m = _re.search(r',\s*"([^"]+)"', val) val = m.group(1) if m else "" if val.startswith("ocid1.") and len(val) > 30: val = val[:20] + "..." + val[-8:] if len(val) > 50: val = val[:47] + "..." row_vals.append(val) res_data.append(tuple(row_vals)) if res_data: _add_heading_red(doc, "Affected Resources:", level=3) n = len(cols) cw = [6.5 / n] * n _styled_table(doc, [c.replace("_", " ").title() for c in cols], res_data, col_widths=cw, header_color=FINDINGS_HEADER_GREEN) if len(csv_rows) > 10: p = doc.add_paragraph(f"Showing 10 of {len(csv_rows)} resources.") p.runs[0].font.size = Pt(8) p.runs[0].font.color.rgb = MUTED except Exception as e: log.warning(f"DOCX: resource table error for {csv_filename}: {e}") # ═══════════════════════════════════════════════ # ── OCI SERVICES (from same data as HTML report) ── # ═══════════════════════════════════════════════ try: if oci_services: svc_data = [] for svc in oci_services: st = svc.get("status", "WARNING") desc = svc.get("description", "") st_color = GREEN if st == "OK" else ORANGE svc_data.append(((st, True, st_color), svc["service"], desc)) doc.add_page_break() _add_heading_red(doc, "OCI SERVICES", level=1) _add_heading_red(doc, "Recommendations:", level=2) _add_body_para(doc, "The recommended services are already enabled. This way we recommend further development of their usage:", space_after=Pt(8), align=WD_ALIGN_PARAGRAPH.LEFT) _styled_table(doc, ["STATUS", "SERVICE", "Description"], svc_data, col_widths=[0.8, 2.0, 3.7]) # Service detail descriptions for svc in oci_services: info = _OCI_SERVICE_DESCRIPTIONS.get(svc["service"], {}) if not info: continue _add_heading_red(doc, info.get("title", svc["service"]), level=2) desc = info.get("description", "") for para in desc.split("\n"): para = para.strip() if para: _add_body_para(doc, para, space_after=Pt(4)) rec_text = info.get("recommendation", "") if rec_text: p = doc.add_paragraph() p.paragraph_format.line_spacing = 1.15 r = p.add_run("Recommendation: ") r.bold = True r.font.name = 'Calibri' r.font.color.rgb = BODY_TEXT r = p.add_run(rec_text) r.font.name = 'Calibri' r.font.color.rgb = BODY_TEXT # Cloud Guard recommendations table cg_recs = svc.get("recommendations", []) if cg_recs: cg_data = [(cr["name"], (str(cr["count"]), True, None)) for cr in cg_recs] _styled_table(doc, ["Recommendations", "Total"], cg_data, col_widths=[5.5, 0.8]) except Exception as e: log.warning(f"DOCX: OCI services section error: {e}") # ═══════════════════════════════════════════════ # ── BACK PAGE ── # ═══════════════════════════════════════════════ doc.add_page_break() _add_heading_red(doc, "Connect with us", level=1) _add_body_para(doc, "Call +1.800.ORACLE1 or visit oracle.com.\nOutside North America, find your local office at: oracle.com/contact.", space_after=Pt(12), align=WD_ALIGN_PARAGRAPH.LEFT) p = doc.add_paragraph() p.paragraph_format.space_before = Pt(8) p.paragraph_format.space_after = Pt(4) for icon, link in [("Blog", "blogs.oracle.com"), ("Facebook", "facebook.com/oracle"), ("Twitter", "twitter.com/oracle")]: r = p.add_run(f" {icon}: {link} ") r.font.size = Pt(9) r.font.color.rgb = MUTED r.font.name = 'Calibri' p = doc.add_paragraph() p.paragraph_format.space_before = Pt(24) p.paragraph_format.space_after = Pt(0) r = p.add_run( f"Copyright \u00A9 {now.year}, Oracle and/or its affiliates. This document is provided for information purposes only, " "and the contents hereof are subject to change without notice. This document is not warranted to be error-free, " "nor subject to any other warranties or conditions, whether expressed orally or implied in law. " "Oracle, Java, MySQL, and NetSuite are registered trademarks of Oracle and/or its affiliates." ) r.font.size = Pt(7) r.font.color.rgb = MUTED r.font.name = 'Calibri' buf = _io.BytesIO() doc.save(buf) return buf.getvalue()