- Remove AddToFolderMenu from grid/list/table library views (folders removed) - Add single "Run" button to MaintenanceFlowDetailPage alongside Batch Launch - Change seed script to publish maintenance flows (batch rejects drafts) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
869 lines
44 KiB
Python
869 lines
44 KiB
Python
#!/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"] = "published"
|
|
|
|
# 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()
|