diff --git a/backend/scripts/seed_maintenance_flows.py b/backend/scripts/seed_maintenance_flows.py new file mode 100644 index 00000000..e792b4f8 --- /dev/null +++ b/backend/scripts/seed_maintenance_flows.py @@ -0,0 +1,868 @@ +#!/usr/bin/env python3 +""" +Maintenance Flow Seed Script for ResolutionFlow. + +Creates sample maintenance flows (scheduled multi-target tasks) for common MSP +infrastructure maintenance work. + +Run from the backend directory with: + python -m scripts.seed_maintenance_flows --email admin@resolutionflow.example.com --password TestPass123! + +Requirements: +- Backend server must be running (uvicorn app.main:app) +""" + +import asyncio +import argparse +import httpx +from typing import Any + + +# API Configuration +API_BASE_URL = "http://localhost:8000/api/v1" + +ADMIN_EMAIL = None +ADMIN_PASSWORD = None + + +# ============================================================================= +# MAINTENANCE FLOW DEFINITIONS +# ============================================================================= + +def get_sonicwall_firmware_flow() -> dict[str, Any]: + """SonicWall Firewall Firmware Update — check and install latest firmware.""" + return { + "name": "SonicWall Firmware Update", + "description": "Check for and install the latest firmware on SonicWall firewalls. Includes pre-flight backup, compatibility check, staged install, and post-update verification. Designed for batch execution across multiple client firewalls.", + "tree_type": "maintenance", + "tags": ["sonicwall", "firmware", "firewall", "maintenance", "security"], + "intake_form": [ + { + "variable_name": "firewall_ip", + "label": "Firewall Management IP", + "field_type": "ip_address", + "required": True, + "placeholder": "e.g. 192.168.1.1", + "help_text": "Management IP or FQDN of the SonicWall appliance", + "group_name": "Firewall Details", + "display_order": 1, + }, + { + "variable_name": "firewall_model", + "label": "SonicWall Model", + "field_type": "select", + "required": True, + "options": [ + "TZ270 / TZ270W", + "TZ370 / TZ370W", + "TZ470 / TZ470W", + "TZ570 / TZ570W", + "TZ670", + "NSa 2700", + "NSa 3700", + "NSa 4700", + "NSa 5700", + "NSa 6700", + "NSsp 10700 / 11700 / 13700", + "NSv (Virtual)", + "Other / Legacy", + ], + "group_name": "Firewall Details", + "display_order": 2, + }, + { + "variable_name": "current_firmware", + "label": "Current Firmware Version", + "field_type": "text", + "required": False, + "placeholder": "e.g. 7.0.1-5145", + "help_text": "Check under System > Status in the SonicWall UI", + "group_name": "Firewall Details", + "display_order": 3, + }, + { + "variable_name": "maintenance_window", + "label": "Maintenance Window", + "field_type": "text", + "required": True, + "placeholder": "e.g. Sat 10pm-2am EST", + "help_text": "Approved maintenance window for this client", + "group_name": "Scheduling", + "display_order": 4, + }, + { + "variable_name": "client_name", + "label": "Client Name", + "field_type": "text", + "required": True, + "placeholder": "e.g. Contoso Ltd", + "group_name": "Scheduling", + "display_order": 5, + }, + { + "variable_name": "ticket_number", + "label": "Ticket Number", + "field_type": "text", + "required": False, + "placeholder": "e.g. TKT-2024-1234", + "group_name": "Scheduling", + "display_order": 6, + }, + ], + "tree_structure": { + "steps": [ + { + "id": "step_1", + "type": "procedure_step", + "title": "Pre-Flight: Verify Access & Current State", + "content_type": "verification", + "estimated_minutes": 5, + "description": ( + "Log into the SonicWall at **[VAR:firewall_ip]** for **[VAR:client_name]**.\n\n" + "**Verify:**\n" + "1. Navigate to **System > Status** — note current firmware version\n" + "2. Confirm the appliance model matches **[VAR:firewall_model]**\n" + "3. Check **System > Diagnostics > Tech Support Report** — note any existing issues\n" + "4. Verify HA status if applicable (both units should be in sync)\n" + "5. Check VPN tunnel status — note how many tunnels are active\n" + "6. Confirm you are within the maintenance window: **[VAR:maintenance_window]**" + ), + "verification": { + "type": "checkbox", + "prompt": "Logged in, current firmware noted, and within maintenance window?" + }, + }, + { + "id": "step_2", + "type": "procedure_step", + "title": "Export Configuration Backup", + "content_type": "action", + "estimated_minutes": 3, + "description": ( + "**Create a full configuration backup before any changes.**\n\n" + "1. Navigate to **System > Settings > Export Settings**\n" + "2. Click **Export** to download the `.exp` configuration file\n" + "3. Save the file as: `[VAR:client_name]_SonicWall_[VAR:firewall_model]_pre-firmware_YYYY-MM-DD.exp`\n" + "4. Store the backup in the client's documentation folder and password vault\n\n" + "**Also export:**\n" + "- **System > Diagnostics > Tech Support Report** (for rollback reference)\n\n" + "**Critical:** Do NOT proceed without a verified backup file." + ), + "verification": { + "type": "checkbox", + "prompt": "Configuration backup exported and stored securely?" + }, + }, + { + "id": "step_3", + "type": "procedure_step", + "title": "Check for Available Firmware", + "content_type": "action", + "estimated_minutes": 5, + "description": ( + "**Check MySonicWall for the latest firmware:**\n\n" + "1. Navigate to **System > Settings > Firmware & Backups**\n" + "2. If connected to MySonicWall, available firmware will show automatically\n" + "3. Alternatively, log into [mysonicwall.com](https://mysonicwall.com) → **Product Management** → select this appliance → **Firmware**\n\n" + "**Before downloading, check:**\n" + "- Read the **Release Notes** for the new firmware version\n" + "- Look for any known issues that affect **[VAR:firewall_model]**\n" + "- Check if the upgrade path requires intermediate firmware versions\n" + "- Verify the firmware is marked **General Release** (not Early Release unless approved)\n\n" + "**Note the target firmware version in your session notes.**" + ), + "notes_enabled": True, + "verification": { + "type": "checkbox", + "prompt": "Target firmware identified and release notes reviewed?" + }, + }, + { + "id": "step_4", + "type": "procedure_step", + "title": "Upload & Install Firmware", + "content_type": "action", + "estimated_minutes": 15, + "warning_text": "The firewall will reboot during this step. All active connections will drop temporarily.", + "description": ( + "**Install the firmware:**\n\n" + "1. Navigate to **System > Settings > Firmware & Backups**\n" + "2. Upload the downloaded firmware file (or click **Download** if fetching from MySonicWall)\n" + "3. Select **Upload Firmware (with current settings)**\n" + "4. **Important:** Select **Create backup of current firmware** before proceeding\n" + "5. Click **Boot** to begin the upgrade\n\n" + "**The appliance will:**\n" + "- Save the current firmware to the backup partition\n" + "- Flash the new firmware\n" + "- Reboot (takes 3-8 minutes depending on model)\n\n" + "**For HA pairs:** Update the secondary unit first, verify, then failover and update the primary.\n\n" + "**Wait for the appliance to come back online.** Monitor the management IP with continuous ping." + ), + "commands": [ + {"language": "bash", "code": "ping -t [VAR:firewall_ip]", "label": "Monitor reboot (Windows)"}, + {"language": "bash", "code": "ping [VAR:firewall_ip]", "label": "Monitor reboot (Mac/Linux)"}, + ], + "verification": { + "type": "checkbox", + "prompt": "Firmware installed and appliance is back online?" + }, + }, + { + "id": "step_5", + "type": "procedure_step", + "title": "Post-Update Verification", + "content_type": "verification", + "estimated_minutes": 10, + "description": ( + "**Verify the firmware update was successful:**\n\n" + "1. Log into **[VAR:firewall_ip]** — confirm the login page loads\n" + "2. Navigate to **System > Status** — confirm new firmware version\n" + "3. Check **System > Diagnostics > Tech Support Report** for errors\n\n" + "**Verify critical services:**\n" + "- [ ] Internet connectivity passing through the firewall\n" + "- [ ] VPN tunnels re-established (compare count from pre-flight)\n" + "- [ ] NAT rules working (test external-facing services)\n" + "- [ ] Content filtering / security services active\n" + "- [ ] DHCP leases being issued (if SonicWall is DHCP server)\n" + "- [ ] SSL VPN / Global VPN Client connectivity\n" + "- [ ] HA sync status (if applicable)\n\n" + "**If anything is broken:** Reboot to the backup firmware partition via **System > Settings > Firmware & Backups** → Boot backup firmware." + ), + "verification": { + "type": "checkbox", + "prompt": "All services verified and operational on new firmware?" + }, + }, + { + "id": "step_6", + "type": "procedure_step", + "title": "Document & Close", + "content_type": "informational", + "estimated_minutes": 5, + "description": ( + "**Update documentation:**\n\n" + "1. Update the client's asset record with the new firmware version\n" + "2. Note the upgrade date and any issues encountered\n" + "3. Export a fresh **post-update configuration backup**\n" + "4. Update the ticket **[VAR:ticket_number]** with results\n\n" + "**Summary for [VAR:client_name]:**\n" + "- Appliance: **[VAR:firewall_model]** at **[VAR:firewall_ip]**\n" + "- Previous firmware: **[VAR:current_firmware]**\n" + "- New firmware: *(note in session)*\n" + "- Status: Operational" + ), + "verification": { + "type": "checkbox", + "prompt": "Documentation updated and post-upgrade backup stored?" + }, + }, + { + "id": "step_end", + "type": "procedure_end", + "title": "Firmware Update Complete", + "description": ( + "SonicWall firmware update complete for **[VAR:client_name]**.\n\n" + "- Appliance: **[VAR:firewall_model]** at **[VAR:firewall_ip]**\n" + "- All services verified operational\n" + "- Configuration backup stored\n\n" + "**Follow-up:** Monitor for 24-48 hours for any stability issues." + ), + }, + ] + }, + } + + +def get_windows_update_dc_flow() -> dict[str, Any]: + """Windows Server 2022 Domain Controller — Windows Update maintenance.""" + return { + "name": "Windows Updates — Domain Controller (Server 2022)", + "description": "Check for and install Windows updates on a Windows Server 2022 domain controller. Includes AD health checks, DFSR/SYSVOL verification, proper reboot sequencing, and post-update replication validation. Designed for batch execution across multiple DCs.", + "tree_type": "maintenance", + "tags": ["windows-server", "domain-controller", "windows-update", "active-directory", "maintenance"], + "intake_form": [ + { + "variable_name": "server_name", + "label": "Server Name", + "field_type": "text", + "required": True, + "placeholder": "e.g. DC01", + "help_text": "Hostname of the domain controller", + "group_name": "Server Details", + "display_order": 1, + }, + { + "variable_name": "server_ip", + "label": "Server IP Address", + "field_type": "ip_address", + "required": True, + "placeholder": "e.g. 10.0.1.10", + "group_name": "Server Details", + "display_order": 2, + }, + { + "variable_name": "domain_name", + "label": "Domain Name", + "field_type": "text", + "required": True, + "placeholder": "e.g. contoso.local", + "group_name": "Server Details", + "display_order": 3, + }, + { + "variable_name": "is_fsmo_holder", + "label": "FSMO Role Holder?", + "field_type": "select", + "required": True, + "options": ["No — standard DC", "Yes — holds one or more FSMO roles", "Unknown — will check"], + "help_text": "FSMO holders need extra care during patching", + "group_name": "Server Details", + "display_order": 4, + }, + { + "variable_name": "maintenance_window", + "label": "Maintenance Window", + "field_type": "text", + "required": True, + "placeholder": "e.g. Sun 2am-6am EST", + "group_name": "Scheduling", + "display_order": 5, + }, + { + "variable_name": "client_name", + "label": "Client Name", + "field_type": "text", + "required": True, + "placeholder": "e.g. Contoso Ltd", + "group_name": "Scheduling", + "display_order": 6, + }, + { + "variable_name": "ticket_number", + "label": "Ticket Number", + "field_type": "text", + "required": False, + "placeholder": "e.g. TKT-2024-1234", + "group_name": "Scheduling", + "display_order": 7, + }, + ], + "tree_structure": { + "steps": [ + { + "id": "step_1", + "type": "procedure_step", + "title": "Pre-Flight: AD Health Check", + "content_type": "verification", + "estimated_minutes": 10, + "description": ( + "Connect to **[VAR:server_name]** (**[VAR:server_ip]**) for **[VAR:client_name]** via RDP or remote PowerShell.\n\n" + "**Run the following health checks before patching:**" + ), + "commands": [ + {"language": "powershell", "code": "# Check AD replication status\nrepadmin /replsummary\n\n# Check for replication failures\nrepadmin /showrepl", "label": "AD Replication Status"}, + {"language": "powershell", "code": "# Run DC diagnostics\ndcdiag /v | Select-String -Pattern 'passed|failed'", "label": "DC Diagnostics"}, + {"language": "powershell", "code": "# Check SYSVOL/NETLOGON shares\nnet share | Select-String 'SYSVOL|NETLOGON'\n\n# Check DFSR health\nGet-WinEvent -LogName 'DFS Replication' -MaxEvents 10 | Format-Table TimeCreated, Message -Wrap", "label": "SYSVOL & DFSR Check"}, + {"language": "powershell", "code": "# Check FSMO roles on this DC\nnetdom query fsmo", "label": "FSMO Role Check"}, + {"language": "powershell", "code": "# Check disk space (need at least 10GB free on C:)\nGet-PSDrive C | Select-Object @{N='FreeGB';E={[math]::Round($_.Free/1GB,2)}}", "label": "Disk Space Check"}, + ], + "verification": { + "type": "checkbox", + "prompt": "AD replication healthy, dcdiag passing, SYSVOL shared, sufficient disk space?" + }, + }, + { + "id": "step_2", + "type": "procedure_step", + "title": "Create System State Backup", + "content_type": "action", + "estimated_minutes": 15, + "description": ( + "**Create a system state backup before patching.**\n\n" + "This captures Active Directory, SYSVOL, registry, and boot files — essential for DC recovery." + ), + "commands": [ + {"language": "powershell", "code": "# Start system state backup (requires Windows Server Backup feature)\nwbadmin start systemstatebackup -backupTarget:C: -quiet", "label": "System State Backup"}, + {"language": "powershell", "code": "# If Windows Server Backup not installed:\nInstall-WindowsFeature Windows-Server-Backup -IncludeManagementTools", "label": "Install Backup Feature (if needed)"}, + ], + "verification": { + "type": "checkbox", + "prompt": "System state backup completed successfully?" + }, + }, + { + "id": "step_3", + "type": "procedure_step", + "title": "Check for & Review Available Updates", + "content_type": "action", + "estimated_minutes": 10, + "description": ( + "**Scan for available updates and review before installing:**" + ), + "commands": [ + {"language": "powershell", "code": "# Install PSWindowsUpdate module if not present\nif (-not (Get-Module -ListAvailable PSWindowsUpdate)) {\n Install-Module PSWindowsUpdate -Force -Confirm:$false\n}\nImport-Module PSWindowsUpdate\n\n# Check for available updates\nGet-WindowsUpdate -MicrosoftUpdate | Format-Table -AutoSize KB, Size, Title", "label": "List Available Updates"}, + ], + "notes_enabled": True, + "verification": { + "type": "checkbox", + "prompt": "Available updates reviewed — no known bad patches in the list?" + }, + }, + { + "id": "step_4", + "type": "procedure_step", + "title": "Install Updates & Reboot", + "content_type": "action", + "estimated_minutes": 30, + "warning_text": "The server will reboot. All users authenticating against this DC will failover to another DC. Ensure at least one other DC is available.", + "description": ( + "**Install all approved updates:**\n\n" + "**Important for [VAR:domain_name]:**\n" + "- If this is the **only DC**, schedule downtime — users will lose authentication during reboot\n" + "- If FSMO holder: verify other DCs can service requests during reboot\n" + "- Reboot will take 5-20 minutes depending on update count" + ), + "commands": [ + {"language": "powershell", "code": "# Install all updates and auto-reboot\nInstall-WindowsUpdate -MicrosoftUpdate -AcceptAll -AutoReboot", "label": "Install Updates (auto-reboot)"}, + {"language": "powershell", "code": "# Or install without auto-reboot (manual control)\nInstall-WindowsUpdate -MicrosoftUpdate -AcceptAll -IgnoreReboot\n\n# Then reboot manually when ready\nRestart-Computer -Force", "label": "Install Updates (manual reboot)"}, + {"language": "bash", "code": "ping -t [VAR:server_ip]", "label": "Monitor reboot (Windows)"}, + ], + "verification": { + "type": "checkbox", + "prompt": "Updates installed and server is back online?" + }, + }, + { + "id": "step_5", + "type": "procedure_step", + "title": "Post-Update: Verify AD Health", + "content_type": "verification", + "estimated_minutes": 10, + "description": ( + "**After the server comes back online, verify everything is healthy:**" + ), + "commands": [ + {"language": "powershell", "code": "# Verify replication is working\nrepadmin /replsummary\nrepadmin /showrepl", "label": "AD Replication Check"}, + {"language": "powershell", "code": "# Run DC diagnostics again\ndcdiag /v | Select-String -Pattern 'passed|failed'", "label": "DC Diagnostics"}, + {"language": "powershell", "code": "# Verify SYSVOL/NETLOGON shares\nnet share | Select-String 'SYSVOL|NETLOGON'\n\n# Verify DNS is resolving\nnslookup [VAR:domain_name] [VAR:server_ip]", "label": "Shares & DNS Check"}, + {"language": "powershell", "code": "# Check for pending reboots\nGet-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired' -ErrorAction SilentlyContinue\nGet-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending' -ErrorAction SilentlyContinue", "label": "Pending Reboot Check"}, + {"language": "powershell", "code": "# Verify installed updates\nGet-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 | Format-Table HotFixID, InstalledOn, Description", "label": "Verify Installed Updates"}, + ], + "verification": { + "type": "checkbox", + "prompt": "AD replication healthy, dcdiag passing, DNS resolving, shares accessible?" + }, + }, + { + "id": "step_6", + "type": "procedure_step", + "title": "Document & Close", + "content_type": "informational", + "estimated_minutes": 5, + "description": ( + "**Update documentation:**\n\n" + "1. Record which KB updates were installed\n" + "2. Note the new OS build number: `winver` or `[System.Environment]::OSVersion`\n" + "3. Update the client's patching log\n" + "4. Update ticket **[VAR:ticket_number]**\n\n" + "**Summary for [VAR:client_name]:**\n" + "- Server: **[VAR:server_name]** (**[VAR:server_ip]**)\n" + "- Domain: **[VAR:domain_name]**\n" + "- FSMO: **[VAR:is_fsmo_holder]**\n" + "- Status: Patched and operational" + ), + "verification": { + "type": "checkbox", + "prompt": "Patching log updated and ticket documented?" + }, + }, + { + "id": "step_end", + "type": "procedure_end", + "title": "Windows Updates Complete", + "description": ( + "Windows updates installed on **[VAR:server_name]** for **[VAR:client_name]**.\n\n" + "- Domain: **[VAR:domain_name]**\n" + "- AD replication: Healthy\n" + "- All services verified operational\n\n" + "**Follow-up:** Monitor event logs for 24 hours for any post-patch issues." + ), + }, + ] + }, + } + + +def get_ssl_cert_renewal_flow() -> dict[str, Any]: + """SSL Certificate Renewal — renew and install SSL certs for client websites.""" + return { + "name": "SSL Certificate Renewal", + "description": "Renew and install SSL/TLS certificates for client websites and web applications. Covers CSR generation, certificate purchase/renewal, installation, and validation. Designed for batch execution across multiple client domains.", + "tree_type": "maintenance", + "tags": ["ssl", "certificate", "tls", "website", "maintenance", "security"], + "intake_form": [ + { + "variable_name": "domain_name", + "label": "Domain Name", + "field_type": "text", + "required": True, + "placeholder": "e.g. www.contoso.com", + "help_text": "Primary domain the certificate covers", + "group_name": "Certificate Details", + "display_order": 1, + }, + { + "variable_name": "cert_type", + "label": "Certificate Type", + "field_type": "select", + "required": True, + "options": [ + "Single Domain (DV)", + "Wildcard (*.domain.com)", + "Multi-Domain / SAN", + "Extended Validation (EV)", + "Let's Encrypt (Free/Auto)", + ], + "group_name": "Certificate Details", + "display_order": 2, + }, + { + "variable_name": "hosting_type", + "label": "Hosting / Server Type", + "field_type": "select", + "required": True, + "options": [ + "IIS (Windows Server)", + "Apache (Linux)", + "Nginx (Linux)", + "cPanel / Plesk", + "Azure App Service", + "AWS (ELB / CloudFront)", + "Cloudflare", + "Other", + ], + "group_name": "Server Details", + "display_order": 3, + }, + { + "variable_name": "server_ip", + "label": "Server IP / Hostname", + "field_type": "text", + "required": True, + "placeholder": "e.g. 203.0.113.50 or web01.contoso.com", + "help_text": "IP or hostname of the web server hosting this site", + "group_name": "Server Details", + "display_order": 4, + }, + { + "variable_name": "cert_provider", + "label": "Certificate Provider", + "field_type": "select", + "required": True, + "options": [ + "Let's Encrypt (Certbot)", + "DigiCert", + "Sectigo / Comodo", + "GoDaddy", + "GlobalSign", + "Namecheap", + "Other", + ], + "group_name": "Certificate Details", + "display_order": 5, + }, + { + "variable_name": "expiry_date", + "label": "Current Cert Expiry Date", + "field_type": "text", + "required": False, + "placeholder": "e.g. 2026-03-15", + "help_text": "When the current certificate expires (YYYY-MM-DD)", + "group_name": "Certificate Details", + "display_order": 6, + }, + { + "variable_name": "client_name", + "label": "Client Name", + "field_type": "text", + "required": True, + "placeholder": "e.g. Contoso Ltd", + "group_name": "Project Info", + "display_order": 7, + }, + { + "variable_name": "ticket_number", + "label": "Ticket Number", + "field_type": "text", + "required": False, + "placeholder": "e.g. TKT-2024-1234", + "group_name": "Project Info", + "display_order": 8, + }, + ], + "tree_structure": { + "steps": [ + { + "id": "step_1", + "type": "procedure_step", + "title": "Pre-Flight: Check Current Certificate", + "content_type": "verification", + "estimated_minutes": 5, + "description": ( + "**Verify the current certificate status for [VAR:domain_name] ([VAR:client_name]):**\n\n" + "Check the current certificate from the command line or browser:" + ), + "commands": [ + {"language": "bash", "code": "# Check current certificate details and expiry\nopenssl s_client -connect [VAR:domain_name]:443 -servername [VAR:domain_name] 2>/dev/null | openssl x509 -noout -dates -subject -issuer", "label": "Check Current Cert (Linux/Mac)"}, + {"language": "powershell", "code": "# Check current certificate from Windows\n$uri = 'https://[VAR:domain_name]'\n$req = [Net.HttpWebRequest]::Create($uri)\n$req.AllowAutoRedirect = $false\ntry { $req.GetResponse() | Out-Null } catch {}\n$cert = $req.ServicePoint.Certificate\nWrite-Host \"Subject: $($cert.Subject)\"\nWrite-Host \"Issuer: $($cert.Issuer)\"\nWrite-Host \"Expires: $($cert.GetExpirationDateString())\"", "label": "Check Current Cert (Windows)"}, + {"language": "bash", "code": "# Quick SSL test via curl\ncurl -vI https://[VAR:domain_name] 2>&1 | grep -E 'expire|subject|issuer|SSL'", "label": "Quick SSL Check"}, + ], + "verification": { + "type": "checkbox", + "prompt": "Current certificate status verified — expiry date confirmed?" + }, + }, + { + "id": "step_2", + "type": "procedure_step", + "title": "Generate CSR (if required)", + "content_type": "action", + "estimated_minutes": 10, + "description": ( + "**Generate a Certificate Signing Request (CSR) if needed.**\n\n" + "Skip this step if using **Let's Encrypt** (certbot handles CSR automatically) or if the provider generates it for you.\n\n" + "Generate the CSR on the target server (**[VAR:server_ip]**):" + ), + "commands": [ + {"language": "bash", "code": "# Generate private key and CSR (Linux/Apache/Nginx)\nopenssl req -new -newkey rsa:2048 -nodes \\\n -keyout [VAR:domain_name].key \\\n -out [VAR:domain_name].csr \\\n -subj \"/C=US/ST=State/L=City/O=[VAR:client_name]/CN=[VAR:domain_name]\"", "label": "Generate CSR (OpenSSL)"}, + {"language": "powershell", "code": "# Generate CSR on IIS (Windows)\n# Open IIS Manager > Server Certificates > Create Certificate Request\n# Or via PowerShell:\n$params = @{\n Subject = 'CN=[VAR:domain_name],O=[VAR:client_name]'\n KeyLength = 2048\n HashAlgorithm = 'SHA256'\n CertStoreLocation = 'Cert:\\LocalMachine\\My'\n}\n$cert = New-SelfSignedCertificate @params\n# Then export CSR via certreq or IIS Manager", "label": "Generate CSR (IIS/Windows)"}, + ], + "verification": { + "type": "checkbox", + "prompt": "CSR generated (or skipped if using auto-renewal)?" + }, + }, + { + "id": "step_3", + "type": "procedure_step", + "title": "Purchase / Renew Certificate", + "content_type": "action", + "estimated_minutes": 15, + "description": ( + "**Renew or purchase the certificate from [VAR:cert_provider]:**\n\n" + "**For Let's Encrypt:**" + ), + "commands": [ + {"language": "bash", "code": "# Let's Encrypt renewal via Certbot\nsudo certbot renew --cert-name [VAR:domain_name]\n\n# Or for a new cert:\nsudo certbot certonly --webroot -w /var/www/html -d [VAR:domain_name]", "label": "Certbot Renewal"}, + ], + "notes_enabled": True, + "verification": { + "type": "checkbox", + "prompt": "New certificate obtained — cert files downloaded/generated?" + }, + }, + { + "id": "step_4", + "type": "procedure_step", + "title": "Install Certificate on Server", + "content_type": "action", + "estimated_minutes": 10, + "description": ( + "**Install the new certificate on [VAR:hosting_type] at [VAR:server_ip]:**\n\n" + "Follow the appropriate process for your hosting type. After installation, restart the web service to load the new certificate." + ), + "commands": [ + {"language": "bash", "code": "# Apache — update ssl.conf and restart\nsudo cp [VAR:domain_name].crt /etc/ssl/certs/\nsudo cp [VAR:domain_name].key /etc/ssl/private/\nsudo systemctl restart apache2", "label": "Install on Apache"}, + {"language": "bash", "code": "# Nginx — update server block and restart\nsudo cp [VAR:domain_name].crt /etc/nginx/ssl/\nsudo cp [VAR:domain_name].key /etc/nginx/ssl/\nsudo nginx -t && sudo systemctl restart nginx", "label": "Install on Nginx"}, + {"language": "powershell", "code": "# IIS — Import certificate via PowerShell\n$cert = Import-PfxCertificate -FilePath 'C:\\certs\\[VAR:domain_name].pfx' -CertStoreLocation 'Cert:\\LocalMachine\\My' -Password (ConvertTo-SecureString -String 'YourPfxPassword' -AsPlainText -Force)\n\n# Then bind in IIS Manager:\n# Sites > [site] > Bindings > Edit HTTPS > Select new certificate", "label": "Install on IIS"}, + ], + "verification": { + "type": "checkbox", + "prompt": "Certificate installed and web service restarted?" + }, + }, + { + "id": "step_5", + "type": "procedure_step", + "title": "Validate New Certificate", + "content_type": "verification", + "estimated_minutes": 5, + "description": ( + "**Verify the new certificate is active and valid:**" + ), + "commands": [ + {"language": "bash", "code": "# Verify the new cert is serving\nopenssl s_client -connect [VAR:domain_name]:443 -servername [VAR:domain_name] 2>/dev/null | openssl x509 -noout -dates -subject\n\n# Verify certificate chain is complete\nopenssl s_client -connect [VAR:domain_name]:443 -servername [VAR:domain_name] 2>/dev/null | grep -E 'Verify return|depth'", "label": "Verify New Cert"}, + {"language": "bash", "code": "# Test HTTPS response\ncurl -I https://[VAR:domain_name]", "label": "Test HTTPS"}, + ], + "reference_url": "https://www.ssllabs.com/ssltest/", + "verification": { + "type": "checkbox", + "prompt": "New certificate verified — correct domain, valid dates, complete chain?" + }, + }, + { + "id": "step_6", + "type": "procedure_step", + "title": "Document & Close", + "content_type": "informational", + "estimated_minutes": 3, + "description": ( + "**Update documentation:**\n\n" + "1. Record the new certificate expiry date\n" + "2. Update the client's certificate inventory\n" + "3. Set a calendar reminder for 30 days before the new expiry\n" + "4. Store any private keys securely in the password vault\n" + "5. Update ticket **[VAR:ticket_number]**\n\n" + "**Summary for [VAR:client_name]:**\n" + "- Domain: **[VAR:domain_name]**\n" + "- Type: **[VAR:cert_type]**\n" + "- Provider: **[VAR:cert_provider]**\n" + "- Server: **[VAR:hosting_type]** at **[VAR:server_ip]**\n" + "- Previous expiry: **[VAR:expiry_date]**\n" + "- New expiry: *(note in session)*\n" + "- Status: Renewed and operational" + ), + "verification": { + "type": "checkbox", + "prompt": "Certificate inventory updated and renewal reminder set?" + }, + }, + { + "id": "step_end", + "type": "procedure_end", + "title": "SSL Certificate Renewed", + "description": ( + "SSL certificate renewed for **[VAR:domain_name]** (**[VAR:client_name]**).\n\n" + "- Certificate type: **[VAR:cert_type]**\n" + "- Provider: **[VAR:cert_provider]**\n" + "- Hosting: **[VAR:hosting_type]**\n" + "- All validations passed\n\n" + "**Follow-up:** Verify no mixed-content warnings and test from multiple browsers." + ), + }, + ] + }, + } + + +# ============================================================================= +# SEEDING FUNCTIONS +# ============================================================================= + +async def get_admin_token(client: httpx.AsyncClient) -> str: + """Authenticate and get admin token.""" + response = await client.post( + f"{API_BASE_URL}/auth/login", + data={"username": ADMIN_EMAIL, "password": ADMIN_PASSWORD}, + ) + if response.status_code != 200: + raise Exception(f"Login failed ({response.status_code}): {response.text}") + return response.json()["access_token"] + + +async def create_maintenance_flow(client: httpx.AsyncClient, token: str, flow_data: dict) -> dict | None: + """Create a maintenance flow via the API. Returns None if it already exists.""" + headers = {"Authorization": f"Bearer {token}"} + + flow_data["is_default"] = True + flow_data["is_public"] = True + flow_data["status"] = "draft" + + # Check if flow with same name exists + list_response = await client.get(f"{API_BASE_URL}/trees", headers=headers, params={"tree_type": "maintenance"}) + if list_response.status_code == 200: + existing = list_response.json() + for tree in existing: + if tree["name"] == flow_data["name"]: + print(f" [SKIP] Flow '{flow_data['name']}' already exists (ID: {tree['id']})") + return None + + response = await client.post( + f"{API_BASE_URL}/trees", + json=flow_data, + headers=headers, + ) + + if response.status_code not in (200, 201): + raise Exception(f"Failed to create flow '{flow_data['name']}': {response.text}") + + tree = response.json() + print(f" [OK] Created flow '{flow_data['name']}' (ID: {tree['id']})") + return tree + + +async def seed_maintenance_flows(): + """Main seeding function.""" + print("\n" + "=" * 60) + print(" RESOLUTIONFLOW - Maintenance Flow Templates Seeder") + print("=" * 60) + + async with httpx.AsyncClient(timeout=60.0) as client: + try: + health_check = await client.get(f"{API_BASE_URL.replace('/api/v1', '')}/health") + if health_check.status_code != 200: + print(f"\n[ERROR] API health check failed: {health_check.status_code}") + return False + except httpx.ConnectError: + print("\n[ERROR] Cannot connect to API server") + print(f" Make sure the server is running at {API_BASE_URL}") + return False + + print("\n[1/3] Authenticating...") + try: + token = await get_admin_token(client) + print(f" Logged in as {ADMIN_EMAIL}") + except Exception as e: + print(f" [ERROR] Failed to authenticate: {e}") + return False + + print("\n[2/3] Preparing maintenance flows...") + flows_to_create = [ + get_sonicwall_firmware_flow(), + get_windows_update_dc_flow(), + get_ssl_cert_renewal_flow(), + ] + print(f" Found {len(flows_to_create)} flows to seed\n") + + print("[3/3] Creating maintenance flows...") + created_count = 0 + skipped_count = 0 + + for flow_data in flows_to_create: + try: + result = await create_maintenance_flow(client, token, flow_data) + if result: + created_count += 1 + else: + skipped_count += 1 + except Exception as e: + print(f" [FAIL] Failed to create '{flow_data['name']}': {e}") + + print("\n" + "=" * 60) + print(" SEEDING COMPLETE") + print("=" * 60) + print(f" Flows created: {created_count}") + print(f" Flows skipped (already exist): {skipped_count}") + print() + + return True + + +def main(): + global ADMIN_EMAIL, ADMIN_PASSWORD, API_BASE_URL + + parser = argparse.ArgumentParser(description="Seed maintenance flow templates") + parser.add_argument("--email", required=True, help="Admin email for authentication") + parser.add_argument("--password", required=True, help="Admin password") + parser.add_argument("--api-url", default=API_BASE_URL, help="API base URL") + args = parser.parse_args() + + ADMIN_EMAIL = args.email + ADMIN_PASSWORD = args.password + API_BASE_URL = args.api_url + + asyncio.run(seed_maintenance_flows()) + + +if __name__ == "__main__": + main()