import gradio as gr import plotly.graph_objects as go from datetime import datetime import json # Security categories and checks SECURITY_CHECKS = { "identity_access": { "en": "Identity & Access", "fr": "Identite et Acces", "checks": [ {"en": "MFA enabled for all users", "fr": "MFA activee pour tous les utilisateurs"}, {"en": "Conditional Access policies configured", "fr": "Politiques d'acces conditionnel configurees"}, {"en": "Legacy authentication blocked", "fr": "Authentification heritee bloquee"}, {"en": "Privileged account protection enabled", "fr": "Protection des comptes privilegies activee"}, {"en": "Password policy enforced (minimum 14 chars)", "fr": "Politique de mot de passe appliquee (14 caracteres min)"}, {"en": "Sign-in risk policies configured", "fr": "Politiques de risque de connexion configurees"}, {"en": "User risk policies configured", "fr": "Politiques de risque utilisateur configurees"}, {"en": "Guest access restrictions applied", "fr": "Restrictions d'acces invites appliquees"}, ] }, "data_protection": { "en": "Data Protection", "fr": "Protection des donnees", "checks": [ {"en": "DLP policies enabled", "fr": "Politiques DLP activees"}, {"en": "Sensitivity labels configured", "fr": "Etiquettes de sensibilite configurees"}, {"en": "Encryption at rest enabled", "fr": "Chiffrement au repos active"}, {"en": "Encryption in transit enforced", "fr": "Chiffrement en transit applique"}, {"en": "External sharing restricted", "fr": "Partage externe restreint"}, {"en": "Data classification implemented", "fr": "Classification des donnees implementee"}, {"en": "Privileged access workstations (PAW) deployed", "fr": "Postes de travail d'acces privilege (PAW) deployes"}, {"en": "Data loss prevention monitored", "fr": "Prevention des pertes de donnees surveillee"}, ] }, "email_security": { "en": "Email Security", "fr": "Securite des emails", "checks": [ {"en": "Anti-phishing policies enabled", "fr": "Politiques anti-phishing activees"}, {"en": "Anti-spam filtering configured", "fr": "Filtrage anti-spam configure"}, {"en": "Safe Links protection enabled", "fr": "Protection Safe Links activee"}, {"en": "Safe Attachments enabled", "fr": "Safe Attachments activee"}, {"en": "DMARC configured", "fr": "DMARC configure"}, {"en": "SPF records configured", "fr": "Enregistrements SPF configures"}, {"en": "DKIM enabled", "fr": "DKIM active"}, {"en": "Mail encryption enabled", "fr": "Chiffrement des emails active"}, {"en": "Malware detection enabled", "fr": "Detection des malwares activee"}, {"en": "External email tagging enabled", "fr": "Marquage des emails externes active"}, ] }, "app_security": { "en": "Application Security", "fr": "Securite des applications", "checks": [ {"en": "App consent policies configured", "fr": "Politiques de consentement d'application configurees"}, {"en": "OAuth app restrictions enforced", "fr": "Restrictions d'application OAuth appliquees"}, {"en": "API permissions audited", "fr": "Permissions d'API auditees"}, {"en": "Third-party app access monitored", "fr": "Acces d'application tierce surveille"}, {"en": "Risky app detection enabled", "fr": "Detection d'application risquee activee"}, {"en": "Application credential protection enabled", "fr": "Protection des identifiants d'application activee"}, {"en": "API throttling configured", "fr": "Limitation de l'API configuree"}, {"en": "App connector security hardened", "fr": "Securite des connecteurs d'application durcie"}, ] }, "monitoring_audit": { "en": "Monitoring & Audit", "fr": "Surveillance et audit", "checks": [ {"en": "Unified Audit Log enabled", "fr": "Journal d'audit unifie active"}, {"en": "Alert policies configured", "fr": "Politiques d'alerte configurees"}, {"en": "Sentinel integration enabled", "fr": "Integration Sentinel activee"}, {"en": "Advanced Audit enabled", "fr": "Audit avance active"}, {"en": "User activity monitoring enabled", "fr": "Surveillance de l'activite utilisateur activee"}, {"en": "Admin activity logging enabled", "fr": "Journalisation de l'activite administrateur activee"}, {"en": "Cloud app security configured", "fr": "Securite des applications cloud configuree"}, {"en": "Anomaly detection enabled", "fr": "Detection des anomalies activee"}, {"en": "Incident response procedures defined", "fr": "Procedures de reponse aux incidents definies"}, {"en": "Regular log review process established", "fr": "Processus d'examen regulier des journaux etabli"}, ] }, "compliance": { "en": "Compliance", "fr": "Conformite", "checks": [ {"en": "Retention policies configured", "fr": "Politiques de retention configurees"}, {"en": "eDiscovery configured", "fr": "eDiscovery configuree"}, {"en": "Communication Compliance enabled", "fr": "Conformite de la communication activee"}, {"en": "Records Management configured", "fr": "Gestion des enregistrements configuree"}, {"en": "Legal hold capabilities configured", "fr": "Fonctionnalites de conservation legale configurees"}, {"en": "Information barriers configured", "fr": "Barrieres informationnelles configurees"}, {"en": "GDPR compliance controls enabled", "fr": "Controles de conformite RGPD actives"}, {"en": "Insider risk management enabled", "fr": "Gestion des risques d'initie activee"}, {"en": "Data residency requirements met", "fr": "Exigences de residencalite des donnees respectees"}, {"en": "Compliance Manager dashboards reviewed", "fr": "Tableaux de bord du gestionnaire de conformite examines"}, ] } } KQL_HUNTING_QUERIES = [ { "title": "Detect MFA Bypass Attempts", "query": "SigninLogs | where ConditionalAccessStatus == 'failure' and AuthenticationRequirement == 'multiFactorAuthentication' | summarize Count=count() by UserPrincipalName, IPAddress, ClientAppUsed" }, { "title": "Detect Privilege Escalation via PIM", "query": "AuditLogs | where OperationName has 'Add member' and TargetResources[0].type == 'User' | project TimeGenerated, InitiatedBy, TargetResources, OperationName" }, { "title": "Detect Suspicious Mail Forwarding Rules", "query": "MailEvents | where EventType == 'ForwardingRuleCreated' and SourceSystem == 'Exchange' | project TimeGenerated, Sender, ForwardingAddress, Subject" }, { "title": "Detect Mass File Access or Download", "query": "CloudAppEvents | where ActionType == 'FileDownloaded' | summarize Count=count() by UserId, Timestamp, bin(Timestamp, 5m) | where Count > 50" }, { "title": "Detect Risky OAuth App Consent", "query": "AuditLogs | where OperationName == 'Add application' and ActivityDisplayName contains 'consent' | project TimeGenerated, InitiatedBy, TargetResources, Result" } ] def calculate_scores(checks_state): """Calculate scores per category""" scores = {} recommendations = [] for category_id, category_data in SECURITY_CHECKS.items(): if category_id in checks_state: checked_items = sum(checks_state[category_id]) total_items = len(category_data["checks"]) score = (checked_items / total_items) * 100 if total_items > 0 else 0 scores[category_id] = score # Generate recommendations for unchecked items unchecked = [check for i, check in enumerate(category_data["checks"]) if not checks_state[category_id][i]] for check in unchecked: recommendations.append({ "category": category_data, "check": check, "priority": len(recommendations) + 1 }) overall_score = sum(scores.values()) / len(scores) if scores else 0 return scores, overall_score, recommendations def create_radar_chart(scores, language): """Create radar chart for category scores""" categories_display = [SECURITY_CHECKS[cat_id][language] for cat_id in scores.keys()] values = list(scores.values()) fig = go.Figure(data=go.Scatterpolar( r=values, theta=categories_display, fill='toself', name='Security Score', line=dict(color='#4f46e5'), fillcolor='rgba(79, 70, 229, 0.2)' )) fig.update_layout( polar=dict(radialaxis=dict(visible=True, range=[0, 100])), showlegend=False, height=400, title={"text": "Security Score by Category", "x": 0.5, "xanchor": "center"}, font=dict(size=12) ) return fig def create_gauge_chart(overall_score, language): """Create gauge chart for overall maturity""" fig = go.Figure(go.Indicator( mode="gauge+number+delta", value=overall_score, title={'text': "Overall Security Maturity Score"}, domain={'x': [0, 1], 'y': [0, 1]}, gauge={ 'axis': {'range': [0, 100]}, 'bar': {'color': "darkblue"}, 'steps': [ {'range': [0, 25], 'color': "rgba(255, 0, 0, 0.2)"}, {'range': [25, 50], 'color': "rgba(255, 165, 0, 0.2)"}, {'range': [50, 75], 'color': "rgba(255, 255, 0, 0.2)"}, {'range': [75, 100], 'color': "rgba(0, 255, 0, 0.2)"} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 90 } } )) fig.update_layout(height=400) return fig def create_recommendations_html(recommendations, language, limit=5): """Create recommendations panel""" if not recommendations: return "

All checks passed! Your M365 security posture is strong.

" html = "

Top 5 Priority Fixes

    " for i, rec in enumerate(recommendations[:limit], 1): check_text = rec["check"][language] category_text = rec["category"][language] html += f"
  1. {category_text}: {check_text}
  2. " html += "
" return html def create_kql_queries_html(language): """Create KQL hunting queries section""" html = "

M365 Threat Hunting - KQL Queries

" for i, query_data in enumerate(KQL_HUNTING_QUERIES, 1): html += f"

{i}. {query_data['title']}

" html += f"
{query_data['query']}
" return html def create_resources_html(language): """Create resources section with backlinks""" resources = [ { "title": "Top 10 Tools for Microsoft 365 Security Analysis", "url": "https://ayinedjimi-consultants.fr/top-10-outils-analyse-securite-microsoft-365.html" }, { "title": "Zero Trust Implementation in Microsoft 365", "url": "https://ayinedjimi-consultants.fr/zero-trust-microsoft-365-implementation.html" }, { "title": "Threat Hunting with Microsoft 365 Defender and Sentinel", "url": "https://ayinedjimi-consultants.fr/threat-hunting-microsoft-365-defender-sentinel.html" }, { "title": "Secure M365 Access with Conditional Access and MFA", "url": "https://ayinedjimi-consultants.fr/securiser-acces-microsoft-365-conditional-access-mfa.html" }, { "title": "Automate M365 Security Audit with PowerShell and Graph", "url": "https://ayinedjimi-consultants.fr/automatiser-audit-securite-microsoft-365-powershell-graph.html" }, { "title": "Leveraging Microsoft Graph API for Audit and Monitoring", "url": "https://ayinedjimi-consultants.fr/exploiter-api-microsoft-graph-audit-monitoring.html" }, { "title": "Advanced M365 Audit with Log Correlation", "url": "https://ayinedjimi-consultants.fr/audit-avance-microsoft-365-correlation-journaux-logs.html" }, { "title": "M365 Security Best Practices 2025", "url": "https://ayinedjimi-consultants.fr/meilleures-pratiques-securite-microsoft-365-2025.html" }, { "title": "M365 Compliance: Integrated Tools and Audit", "url": "https://ayinedjimi-consultants.fr/microsoft-365-conformite-outils-integres-audit.html" }, { "title": "Detecting Compromised Identities in Azure AD", "url": "https://ayinedjimi-consultants.fr/microsoft-365-azure-ad-detection-attaques-compromission-identites.html" }, { "title": "Microsoft 365 Audit Guide", "url": "https://ayinedjimi-consultants.fr/audit-microsoft-365.html" } ] html = "

Resources & Learning Materials

" return html def generate_markdown_report(checks_state, scores, overall_score, language): """Generate markdown report for export""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") lang_code = "FR" if language == "fr" else "EN" report = f"# M365 Security Scorecard Report\n\n" report += f"**Report Generated:** {timestamp}\n" report += f"**Language:** {lang_code}\n" report += f"**Overall Security Score:** {overall_score:.1f}%\n\n" report += "## Category Scores\n\n" for category_id, score in scores.items(): cat_name = SECURITY_CHECKS[category_id][language] report += f"- **{cat_name}:** {score:.1f}%\n" report += "\n## Detailed Assessment\n\n" for category_id, category_data in SECURITY_CHECKS.items(): cat_name = category_data[language] report += f"### {cat_name}\n\n" if category_id in checks_state: for i, check in enumerate(category_data["checks"]): check_text = check[language] status = "✓ PASS" if checks_state[category_id][i] else "✗ FAIL" report += f"- {status}: {check_text}\n" report += "\n" report += f"\n---\nGenerated by M365 Security Scorecard\n" report += f"Created by [AYI-NEDJIMI Consultants](https://ayinedjimi-consultants.fr/bio.html)" return report def update_display(language, *check_args): """Update all displays based on checks""" # Convert check_args to a dictionary matching the structure checks_state = {} arg_index = 0 for category_id in SECURITY_CHECKS.keys(): num_checks = len(SECURITY_CHECKS[category_id]["checks"]) checks_state[category_id] = list(check_args[arg_index:arg_index + num_checks]) arg_index += num_checks # Calculate scores scores, overall_score, recommendations = calculate_scores(checks_state) # Create visualizations radar = create_radar_chart(scores, language) gauge = create_gauge_chart(overall_score, language) # Create recommendations recommendations_html = create_recommendations_html(recommendations, language) # Create KQL section kql_html = create_kql_queries_html(language) # Create markdown report markdown_report = generate_markdown_report(checks_state, scores, overall_score, language) return radar, gauge, recommendations_html, kql_html, markdown_report # Create the Gradio interface with gr.Blocks(title="M365 Security Scorecard", theme=gr.themes.Soft(primary_hue="blue", secondary_hue="indigo")) as demo: gr.Markdown("# M365 Security Scorecard - Microsoft 365 Security Assessment") gr.Markdown("Evaluate your Microsoft 365 security posture across six key dimensions. Toggle language, complete the assessment, and receive actionable recommendations.") with gr.Row(): language_radio = gr.Radio( choices=["EN - English", "FR - Francais"], value="EN - English", label="Language / Langue", interactive=True ) # Create tabs for each category with gr.Tabs(): check_components = {} for category_id, category_data in SECURITY_CHECKS.items(): with gr.Tab(label=category_data["en"]): gr.Markdown(f"### {category_data['en']} / {category_data['fr']}") for i, check in enumerate(category_data["checks"]): check_key = f"{category_id}_{i}" check_components[check_key] = gr.Checkbox( label=f"{check['en']} / {check['fr']}", value=False ) # Results tab with gr.Tab(label="Assessment Results"): with gr.Row(): radar_chart = gr.Plot(label="Category Scores") gauge_chart = gr.Plot(label="Overall Score") gr.Markdown("## Recommendations") recommendations_html = gr.HTML() gr.Markdown("## KQL Hunting Queries") kql_html = gr.HTML() # Export tab with gr.Tab(label="Export & Resources"): with gr.Row(): export_btn = gr.Button("Download Markdown Report", variant="primary") markdown_output = gr.Textbox( label="Markdown Report", lines=20, interactive=False, max_lines=50 ) export_btn.click( fn=lambda *args: update_display("EN" if args[0] == "EN - English" else "FR", *args[1:]), inputs=[language_radio] + list(check_components.values()), outputs=[radar_chart, gauge_chart, recommendations_html, kql_html, markdown_output] ) gr.Markdown("## Resources & Learning Materials") resources_html = gr.HTML() # Update resources on load demo.load( fn=lambda lang: create_resources_html("FR" if lang == "FR - Francais" else "EN"), inputs=[language_radio], outputs=[resources_html] ) # Change event to update results in real-time language_radio.change( fn=lambda *args: update_display("EN" if args[0] == "EN - English" else "FR", *args[1:]), inputs=[language_radio] + list(check_components.values()), outputs=[radar_chart, gauge_chart, recommendations_html, kql_html, markdown_output] ) for component in check_components.values(): component.change( fn=lambda *args: update_display("EN" if args[0] == "EN - English" else "FR", *args[1:]), inputs=[language_radio] + list(check_components.values()), outputs=[radar_chart, gauge_chart, recommendations_html, kql_html, markdown_output] ) gr.Markdown("---") gr.Markdown("Created by [AYI-NEDJIMI Consultants](https://ayinedjimi-consultants.fr/bio.html)") if __name__ == "__main__": demo.launch()