Allows running against remote environments like Railway. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1005 lines
57 KiB
Python
1005 lines
57 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Procedural Flow Seed Script for ResolutionFlow.
|
|
|
|
Creates sample procedural flows (step-by-step project templates) with intake forms
|
|
for common MSP project work.
|
|
|
|
Run from the backend directory with: python -m scripts.seed_procedural_flows
|
|
|
|
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
|
|
|
|
|
|
# =============================================================================
|
|
# PROCEDURAL FLOW DEFINITIONS
|
|
# =============================================================================
|
|
|
|
def get_domain_controller_flow() -> dict[str, Any]:
|
|
"""Domain Controller & Active Directory Setup — full build procedure."""
|
|
return {
|
|
"name": "Domain Controller & Active Directory Setup",
|
|
"description": "Complete procedure for building a new domain controller, installing Active Directory Domain Services, configuring DNS, and verifying replication. Includes intake form for server and domain details.",
|
|
"tree_type": "procedural",
|
|
"category": "Projects - Infrastructure",
|
|
"tags": ["active-directory", "dns", "domain-controller", "server-build"],
|
|
"intake_form": [
|
|
{
|
|
"variable_name": "server_name",
|
|
"label": "Server Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. DC01, YOURCOMPANY-DC02",
|
|
"help_text": "NetBIOS name of the new domain controller (max 15 characters)",
|
|
"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",
|
|
"help_text": "Static IP address for this server",
|
|
"group_name": "Server Details",
|
|
"display_order": 2,
|
|
},
|
|
{
|
|
"variable_name": "subnet_mask",
|
|
"label": "Subnet Mask",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. 255.255.255.0",
|
|
"default_value": "255.255.255.0",
|
|
"group_name": "Server Details",
|
|
"display_order": 3,
|
|
},
|
|
{
|
|
"variable_name": "default_gateway",
|
|
"label": "Default Gateway",
|
|
"field_type": "ip_address",
|
|
"required": True,
|
|
"placeholder": "e.g. 10.0.1.1",
|
|
"group_name": "Server Details",
|
|
"display_order": 4,
|
|
},
|
|
{
|
|
"variable_name": "domain_name",
|
|
"label": "Domain Name (FQDN)",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. corp.contoso.com",
|
|
"help_text": "Fully qualified domain name",
|
|
"group_name": "Domain Configuration",
|
|
"display_order": 5,
|
|
},
|
|
{
|
|
"variable_name": "netbios_name",
|
|
"label": "NetBIOS Domain Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. CONTOSO",
|
|
"help_text": "Short domain name (auto-derived from FQDN if unsure)",
|
|
"group_name": "Domain Configuration",
|
|
"display_order": 6,
|
|
},
|
|
{
|
|
"variable_name": "is_first_dc",
|
|
"label": "Is this the first DC in the domain?",
|
|
"field_type": "select",
|
|
"required": True,
|
|
"options": ["Yes — new forest/domain", "No — additional DC in existing domain"],
|
|
"help_text": "Determines whether to create a new forest or join an existing one",
|
|
"group_name": "Domain Configuration",
|
|
"display_order": 7,
|
|
},
|
|
{
|
|
"variable_name": "existing_dc_ip",
|
|
"label": "Existing DC IP (if adding to existing domain)",
|
|
"field_type": "ip_address",
|
|
"required": False,
|
|
"placeholder": "e.g. 10.0.1.5",
|
|
"help_text": "IP of an existing domain controller for replication (leave blank for first DC)",
|
|
"group_name": "Domain Configuration",
|
|
"display_order": 8,
|
|
},
|
|
{
|
|
"variable_name": "dsrm_password",
|
|
"label": "DSRM Password",
|
|
"field_type": "password",
|
|
"required": True,
|
|
"help_text": "Directory Services Restore Mode password — store securely in password vault",
|
|
"group_name": "Security",
|
|
"display_order": 9,
|
|
},
|
|
{
|
|
"variable_name": "server_roles",
|
|
"label": "Additional Server Roles",
|
|
"field_type": "multi_select",
|
|
"required": False,
|
|
"options": ["DHCP Server", "Certificate Services (CA)", "DFS Namespaces", "Windows Server Backup", "WSUS"],
|
|
"help_text": "Optional roles to install alongside AD DS",
|
|
"group_name": "Additional Configuration",
|
|
"display_order": 10,
|
|
},
|
|
{
|
|
"variable_name": "client_name",
|
|
"label": "Client / Company Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Contoso Ltd",
|
|
"group_name": "Project Info",
|
|
"display_order": 11,
|
|
},
|
|
{
|
|
"variable_name": "ticket_number",
|
|
"label": "Ticket / Project Number",
|
|
"field_type": "text",
|
|
"required": False,
|
|
"placeholder": "e.g. PRJ-2024-0042",
|
|
"group_name": "Project Info",
|
|
"display_order": 12,
|
|
},
|
|
],
|
|
"tree_structure": {
|
|
"steps": [
|
|
{
|
|
"id": "step_1",
|
|
"type": "procedure_step",
|
|
"title": "Verify Prerequisites",
|
|
"content_type": "verification",
|
|
"description": "Before beginning, verify the following prerequisites are met for **[VAR:client_name]** project **[VAR:ticket_number]**:\n\n- Windows Server 2022 (or 2019) is installed and activated on **[VAR:server_name]**\n- Server has network connectivity\n- You have local administrator credentials\n- The IP address **[VAR:server_ip]** is not in use (ping test)\n- DNS is planned — if this is a new domain, the DC will be its own DNS server",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "All prerequisites verified?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_2",
|
|
"type": "procedure_step",
|
|
"title": "Configure Static IP Address",
|
|
"content_type": "action",
|
|
"description": "Set the static IP address on the primary network adapter of **[VAR:server_name]**.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Get the primary network adapter\n$adapter = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | Select-Object -First 1\n\n# Remove existing IP configuration\nRemove-NetIPAddress -InterfaceIndex $adapter.ifIndex -Confirm:$false -ErrorAction SilentlyContinue\nRemove-NetRoute -InterfaceIndex $adapter.ifIndex -Confirm:$false -ErrorAction SilentlyContinue\n\n# Set static IP\nNew-NetIPAddress -InterfaceIndex $adapter.ifIndex `\n -IPAddress \"[VAR:server_ip]\" `\n -PrefixLength 24 `\n -DefaultGateway \"[VAR:default_gateway]\"\n\n# Set DNS — point to self (will be DNS server after AD install)\nSet-DnsClientServerAddress -InterfaceIndex $adapter.ifIndex `\n -ServerAddresses \"[VAR:server_ip]\",\"[VAR:default_gateway]\"",
|
|
"label": "Set Static IP via PowerShell"
|
|
}
|
|
],
|
|
"expected_outcome": "Server should have IP **[VAR:server_ip]** with gateway **[VAR:default_gateway]**. Verify with `ipconfig /all`.",
|
|
},
|
|
{
|
|
"id": "step_3",
|
|
"type": "procedure_step",
|
|
"title": "Rename Server",
|
|
"content_type": "action",
|
|
"description": "Rename the server to **[VAR:server_name]** and reboot.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Rename the computer\nRename-Computer -NewName \"[VAR:server_name]\" -Force -Restart",
|
|
"label": "Rename and restart"
|
|
}
|
|
],
|
|
"expected_outcome": "Server reboots with hostname **[VAR:server_name]**. Wait for reboot to complete before proceeding.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Server has rebooted and hostname is correct?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_4",
|
|
"type": "procedure_step",
|
|
"title": "Install AD DS Role",
|
|
"content_type": "action",
|
|
"description": "Install the Active Directory Domain Services role and management tools on **[VAR:server_name]**.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Install AD DS role with management tools\nInstall-WindowsFeature AD-Domain-Services -IncludeManagementTools -Verbose",
|
|
"label": "Install AD DS"
|
|
}
|
|
],
|
|
"expected_outcome": "Feature installation completes successfully. No reboot required at this stage.",
|
|
},
|
|
{
|
|
"id": "step_5",
|
|
"type": "procedure_step",
|
|
"title": "Promote to Domain Controller",
|
|
"content_type": "action",
|
|
"description": "Promote **[VAR:server_name]** to a domain controller for **[VAR:domain_name]**.\n\n**Important:** The DSRM password should be stored securely in your password vault immediately after this step.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# For a NEW forest/domain:\nInstall-ADDSForest `\n -DomainName \"[VAR:domain_name]\" `\n -DomainNetBIOSName \"[VAR:netbios_name]\" `\n -ForestMode \"WinThreshold\" `\n -DomainMode \"WinThreshold\" `\n -InstallDns:$true `\n -DatabasePath \"C:\\Windows\\NTDS\" `\n -LogPath \"C:\\Windows\\NTDS\" `\n -SysvolPath \"C:\\Windows\\SYSVOL\" `\n -SafeModeAdministratorPassword (Read-Host -AsSecureString \"DSRM Password\") `\n -Force:$true",
|
|
"label": "Promote — New Forest"
|
|
},
|
|
{
|
|
"language": "powershell",
|
|
"code": "# For ADDITIONAL DC in existing domain:\nInstall-ADDSDomainController `\n -DomainName \"[VAR:domain_name]\" `\n -InstallDns:$true `\n -DatabasePath \"C:\\Windows\\NTDS\" `\n -LogPath \"C:\\Windows\\NTDS\" `\n -SysvolPath \"C:\\Windows\\SYSVOL\" `\n -SafeModeAdministratorPassword (Read-Host -AsSecureString \"DSRM Password\") `\n -Force:$true",
|
|
"label": "Promote — Additional DC"
|
|
}
|
|
],
|
|
"expected_outcome": "Server reboots automatically after promotion. This may take 5-10 minutes. After reboot, log in with **[VAR:netbios_name]\\Administrator**.",
|
|
},
|
|
{
|
|
"id": "step_6",
|
|
"type": "procedure_step",
|
|
"title": "Verify AD DS Installation",
|
|
"content_type": "verification",
|
|
"description": "After reboot, verify Active Directory and DNS are functioning correctly on **[VAR:server_name]**.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Verify AD DS\nGet-ADDomainController -Filter *\nGet-ADDomain\nGet-ADForest\n\n# Verify DNS\nGet-DnsServerZone\nResolve-DnsName [VAR:domain_name]\n\n# Check SYSVOL and NETLOGON shares\nGet-SmbShare | Where-Object { $_.Name -in 'SYSVOL','NETLOGON' }\n\n# Check DCDIAG\ndcdiag /s:[VAR:server_name]",
|
|
"label": "AD & DNS verification"
|
|
}
|
|
],
|
|
"expected_outcome": "All DCDIAG tests should pass. SYSVOL and NETLOGON shares should be present. DNS zone for **[VAR:domain_name]** should exist.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "DCDIAG passed, SYSVOL/NETLOGON shares present, DNS zone exists?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_7",
|
|
"type": "procedure_step",
|
|
"title": "Configure DNS Forwarders",
|
|
"content_type": "action",
|
|
"description": "Configure DNS forwarders so **[VAR:server_name]** can resolve external domains.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Set DNS forwarders (Cloudflare + Google as fallback)\nSet-DnsServerForwarder -IPAddress \"1.1.1.1\",\"8.8.8.8\" -PassThru\n\n# Verify\nGet-DnsServerForwarder\n\n# Test external resolution\nResolve-DnsName google.com",
|
|
"label": "Configure forwarders"
|
|
}
|
|
],
|
|
"expected_outcome": "External DNS resolution works. `Resolve-DnsName google.com` should return an IP address.",
|
|
},
|
|
{
|
|
"id": "step_8",
|
|
"type": "procedure_step",
|
|
"title": "Create OU Structure",
|
|
"content_type": "action",
|
|
"description": "Create the organizational unit structure for **[VAR:client_name]**.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "$domain = (Get-ADDomain).DistinguishedName\n\n# Create top-level OUs\n$ous = @(\n \"OU=Company Users,$domain\",\n \"OU=Company Computers,$domain\",\n \"OU=Company Groups,$domain\",\n \"OU=Company Servers,$domain\",\n \"OU=Service Accounts,$domain\",\n \"OU=Disabled Accounts,$domain\"\n)\n\nforeach ($ou in $ous) {\n $name = ($ou -split ',')[0] -replace 'OU=',''\n try {\n New-ADOrganizationalUnit -Name $name -Path $domain -ProtectedFromAccidentalDeletion $true\n Write-Host \"Created: $name\" -ForegroundColor Green\n } catch {\n Write-Host \"Already exists: $name\" -ForegroundColor Yellow\n }\n}",
|
|
"label": "Create OUs"
|
|
}
|
|
],
|
|
"expected_outcome": "OU structure visible in Active Directory Users and Computers.",
|
|
},
|
|
{
|
|
"id": "step_9",
|
|
"type": "procedure_step",
|
|
"title": "Configure Group Policy Defaults",
|
|
"content_type": "action",
|
|
"description": "Configure essential Group Policy settings for the new domain.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Set password policy\nSet-ADDefaultDomainPasswordPolicy -Identity \"[VAR:domain_name]\" `\n -LockoutThreshold 5 `\n -LockoutDuration \"00:30:00\" `\n -LockoutObservationWindow \"00:30:00\" `\n -MaxPasswordAge \"90.00:00:00\" `\n -MinPasswordAge \"1.00:00:00\" `\n -MinPasswordLength 12 `\n -PasswordHistoryCount 10 `\n -ComplexityEnabled $true\n\n# Verify\nGet-ADDefaultDomainPasswordPolicy",
|
|
"label": "Set password policy"
|
|
}
|
|
],
|
|
"expected_outcome": "Password policy configured: 12 char minimum, complexity enabled, 5 lockout threshold, 90-day max age.",
|
|
},
|
|
{
|
|
"id": "step_10",
|
|
"type": "procedure_step",
|
|
"title": "Document and Update Password Vault",
|
|
"content_type": "informational",
|
|
"description": "Document the following in the client's IT documentation and password vault:\n\n| Item | Value |\n|------|-------|\n| Server Name | **[VAR:server_name]** |\n| IP Address | **[VAR:server_ip]** |\n| Domain | **[VAR:domain_name]** |\n| NetBIOS | **[VAR:netbios_name]** |\n| DSRM Password | *(stored in vault)* |\n| DNS Forwarders | 1.1.1.1, 8.8.8.8 |\n\nUpdate the client's network diagram and runbook with the new DC information.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Documentation and password vault updated?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_end",
|
|
"type": "procedure_end",
|
|
"title": "Domain Controller Setup Complete",
|
|
"description": "**[VAR:server_name]** has been configured as a domain controller for **[VAR:domain_name]** at **[VAR:client_name]**.\n\n**Next steps to consider:**\n- Configure DHCP if selected as an additional role\n- Set up Azure AD Connect for hybrid identity\n- Configure backup solution for System State\n- Schedule a replication health check in 24 hours",
|
|
},
|
|
]
|
|
},
|
|
}
|
|
|
|
|
|
def get_m365_user_onboarding_flow() -> dict[str, Any]:
|
|
"""Microsoft 365 User Onboarding — new hire setup procedure."""
|
|
return {
|
|
"name": "Microsoft 365 User Onboarding",
|
|
"description": "Step-by-step procedure for creating a new Microsoft 365 user account, assigning licenses, configuring email, adding to security groups, and setting up MFA. Includes intake form for new user details.",
|
|
"tree_type": "procedural",
|
|
"category": "Projects - Microsoft 365",
|
|
"tags": ["m365", "onboarding", "user-setup", "exchange-online"],
|
|
"intake_form": [
|
|
{
|
|
"variable_name": "first_name",
|
|
"label": "First Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Jane",
|
|
"group_name": "User Information",
|
|
"display_order": 1,
|
|
},
|
|
{
|
|
"variable_name": "last_name",
|
|
"label": "Last Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Smith",
|
|
"group_name": "User Information",
|
|
"display_order": 2,
|
|
},
|
|
{
|
|
"variable_name": "display_name",
|
|
"label": "Display Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Jane Smith",
|
|
"group_name": "User Information",
|
|
"display_order": 3,
|
|
},
|
|
{
|
|
"variable_name": "job_title",
|
|
"label": "Job Title",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Marketing Manager",
|
|
"group_name": "User Information",
|
|
"display_order": 4,
|
|
},
|
|
{
|
|
"variable_name": "department",
|
|
"label": "Department",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Marketing",
|
|
"group_name": "User Information",
|
|
"display_order": 5,
|
|
},
|
|
{
|
|
"variable_name": "manager_email",
|
|
"label": "Manager's Email",
|
|
"field_type": "email",
|
|
"required": False,
|
|
"placeholder": "e.g. john.doe@company.com",
|
|
"group_name": "User Information",
|
|
"display_order": 6,
|
|
},
|
|
{
|
|
"variable_name": "email_address",
|
|
"label": "Email Address",
|
|
"field_type": "email",
|
|
"required": True,
|
|
"placeholder": "e.g. jane.smith@company.com",
|
|
"group_name": "Account Setup",
|
|
"display_order": 7,
|
|
},
|
|
{
|
|
"variable_name": "license_type",
|
|
"label": "License Type",
|
|
"field_type": "select",
|
|
"required": True,
|
|
"options": [
|
|
"Microsoft 365 Business Basic",
|
|
"Microsoft 365 Business Standard",
|
|
"Microsoft 365 Business Premium",
|
|
"Microsoft 365 E3",
|
|
"Microsoft 365 E5",
|
|
"Exchange Online Plan 1",
|
|
"Exchange Online Plan 2"
|
|
],
|
|
"help_text": "Select the license to assign to this user",
|
|
"group_name": "Account Setup",
|
|
"display_order": 8,
|
|
},
|
|
{
|
|
"variable_name": "security_groups",
|
|
"label": "Security Groups",
|
|
"field_type": "textarea",
|
|
"required": False,
|
|
"placeholder": "One group per line, e.g.:\nMarketing Team\nAll Staff\nVPN Users",
|
|
"help_text": "List the security/distribution groups this user should be added to",
|
|
"group_name": "Access & Groups",
|
|
"display_order": 9,
|
|
},
|
|
{
|
|
"variable_name": "shared_mailboxes",
|
|
"label": "Shared Mailboxes (Full Access)",
|
|
"field_type": "textarea",
|
|
"required": False,
|
|
"placeholder": "One mailbox per line, e.g.:\ninfo@company.com\nsales@company.com",
|
|
"help_text": "Shared mailboxes this user needs access to",
|
|
"group_name": "Access & Groups",
|
|
"display_order": 10,
|
|
},
|
|
{
|
|
"variable_name": "start_date",
|
|
"label": "Start Date",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. 2025-03-15",
|
|
"help_text": "When the user needs access (YYYY-MM-DD)",
|
|
"group_name": "Project Info",
|
|
"display_order": 11,
|
|
},
|
|
{
|
|
"variable_name": "client_name",
|
|
"label": "Client / Company Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Contoso Ltd",
|
|
"group_name": "Project Info",
|
|
"display_order": 12,
|
|
},
|
|
{
|
|
"variable_name": "ticket_number",
|
|
"label": "Ticket Number",
|
|
"field_type": "text",
|
|
"required": False,
|
|
"placeholder": "e.g. TKT-2024-1234",
|
|
"group_name": "Project Info",
|
|
"display_order": 13,
|
|
},
|
|
],
|
|
"tree_structure": {
|
|
"steps": [
|
|
{
|
|
"id": "step_1",
|
|
"type": "procedure_step",
|
|
"title": "Verify Prerequisites & Licensing",
|
|
"content_type": "verification",
|
|
"description": "Before creating the account for **[VAR:display_name]** at **[VAR:client_name]**:\n\n- Confirm **[VAR:license_type]** license is available in the tenant\n- Verify the domain for **[VAR:email_address]** is configured in M365\n- Confirm the email address is not already in use\n- Verify start date: **[VAR:start_date]**\n- Ticket reference: **[VAR:ticket_number]**",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Connect to Microsoft Graph (if not already connected)\nConnect-MgGraph -Scopes \"User.ReadWrite.All\",\"Group.ReadWrite.All\",\"Directory.ReadWrite.All\"\n\n# Check available licenses\nGet-MgSubscribedSku | Select-Object SkuPartNumber, ConsumedUnits, @{N='Available';E={$_.PrepaidUnits.Enabled - $_.ConsumedUnits}} | Format-Table\n\n# Check if email is already taken\nGet-MgUser -Filter \"mail eq '[VAR:email_address]'\" -ErrorAction SilentlyContinue",
|
|
"label": "Check licenses & email availability"
|
|
}
|
|
],
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "License available and email address is free?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_2",
|
|
"type": "procedure_step",
|
|
"title": "Create User Account",
|
|
"content_type": "action",
|
|
"description": "Create the Microsoft 365 user account for **[VAR:display_name]**.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Generate a temporary password\n$tempPassword = -join ((65..90) + (97..122) + (48..57) + (33,35,36,37) | Get-Random -Count 16 | ForEach-Object { [char]$_ })\n\n# Create the user\n$params = @{\n AccountEnabled = $true\n DisplayName = \"[VAR:display_name]\"\n GivenName = \"[VAR:first_name]\"\n Surname = \"[VAR:last_name]\"\n MailNickname = \"[VAR:first_name].[VAR:last_name]\".ToLower()\n UserPrincipalName = \"[VAR:email_address]\"\n JobTitle = \"[VAR:job_title]\"\n Department = \"[VAR:department]\"\n PasswordProfile = @{\n ForceChangePasswordNextSignIn = $true\n Password = $tempPassword\n }\n UsageLocation = \"US\"\n}\n\n$newUser = New-MgUser @params\nWrite-Host \"User created: $($newUser.UserPrincipalName)\" -ForegroundColor Green\nWrite-Host \"Temp password: $tempPassword\" -ForegroundColor Yellow",
|
|
"label": "Create M365 user"
|
|
}
|
|
],
|
|
"expected_outcome": "User account created. **Save the temporary password securely** — you'll need it for the welcome email.",
|
|
},
|
|
{
|
|
"id": "step_3",
|
|
"type": "procedure_step",
|
|
"title": "Assign License",
|
|
"content_type": "action",
|
|
"description": "Assign the **[VAR:license_type]** license to **[VAR:display_name]**.",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Get the user\n$user = Get-MgUser -Filter \"userPrincipalName eq '[VAR:email_address]'\"\n\n# Get the SKU for the selected license\n# Common SKU mappings:\n# Business Basic = O365_BUSINESS_ESSENTIALS or SPB\n# Business Standard = O365_BUSINESS_PREMIUM\n# Business Premium = SPB\n# E3 = SPE_E3\n# E5 = SPE_E5\n$skus = Get-MgSubscribedSku | Where-Object { $_.PrepaidUnits.Enabled - $_.ConsumedUnits -gt 0 }\n$skus | Select-Object SkuPartNumber, SkuId | Format-Table\n\n# Assign license (replace SKU_ID with actual value from above)\n# Set-MgUserLicense -UserId $user.Id -AddLicenses @(@{SkuId = \"SKU_ID_HERE\"}) -RemoveLicenses @()",
|
|
"label": "Assign license via PowerShell"
|
|
}
|
|
],
|
|
"expected_outcome": "License assigned. Mailbox provisioning may take 5-15 minutes.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "License successfully assigned?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_4",
|
|
"type": "procedure_step",
|
|
"title": "Wait for Mailbox Provisioning",
|
|
"content_type": "informational",
|
|
"description": "Exchange Online mailbox provisioning typically takes 5-15 minutes after license assignment.\n\nWhile waiting, you can proceed to configure groups in the next step.\n\nTo check mailbox status:",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Check if mailbox exists yet\nConnect-ExchangeOnline\nGet-EXOMailbox -Identity \"[VAR:email_address]\" -ErrorAction SilentlyContinue | Select-Object DisplayName, PrimarySmtpAddress, RecipientTypeDetails",
|
|
"label": "Check mailbox provisioning"
|
|
}
|
|
],
|
|
"expected_outcome": "Mailbox appears with `RecipientTypeDetails` = `UserMailbox`.",
|
|
},
|
|
{
|
|
"id": "step_5",
|
|
"type": "procedure_step",
|
|
"title": "Add to Security Groups",
|
|
"content_type": "action",
|
|
"description": "Add **[VAR:display_name]** to the requested security and distribution groups.\n\n**Requested groups:**\n[VAR:security_groups]",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Get the user\n$user = Get-MgUser -Filter \"userPrincipalName eq '[VAR:email_address]'\"\n\n# List available groups (search by name)\n# Get-MgGroup -Filter \"displayName eq 'GROUP_NAME'\" | Select-Object DisplayName, Id\n\n# Add user to a group\n# New-MgGroupMember -GroupId \"GROUP_ID\" -DirectoryObjectId $user.Id",
|
|
"label": "Add to groups"
|
|
}
|
|
],
|
|
"expected_outcome": "User appears as a member of all requested groups.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "User added to all requested groups?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_6",
|
|
"type": "procedure_step",
|
|
"title": "Configure Shared Mailbox Access",
|
|
"content_type": "action",
|
|
"description": "Grant **[VAR:display_name]** Full Access and Send As permissions on requested shared mailboxes.\n\n**Requested shared mailboxes:**\n[VAR:shared_mailboxes]",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Grant Full Access to shared mailbox\n# Add-MailboxPermission -Identity \"SHARED_MAILBOX@company.com\" -User \"[VAR:email_address]\" -AccessRights FullAccess -AutoMapping $true\n\n# Grant Send As permission\n# Add-RecipientPermission -Identity \"SHARED_MAILBOX@company.com\" -Trustee \"[VAR:email_address]\" -AccessRights SendAs -Confirm:$false",
|
|
"label": "Configure shared mailbox access"
|
|
}
|
|
],
|
|
"expected_outcome": "Shared mailboxes auto-map in Outlook within 30-60 minutes.",
|
|
},
|
|
{
|
|
"id": "step_7",
|
|
"type": "procedure_step",
|
|
"title": "Set Manager (Optional)",
|
|
"content_type": "action",
|
|
"description": "Set the manager for **[VAR:display_name]** if provided.\n\nManager: **[VAR:manager_email]**",
|
|
"commands": [
|
|
{
|
|
"language": "powershell",
|
|
"code": "# Set manager\n$user = Get-MgUser -Filter \"userPrincipalName eq '[VAR:email_address]'\"\n$manager = Get-MgUser -Filter \"userPrincipalName eq '[VAR:manager_email]'\"\n\nif ($manager) {\n Set-MgUserManagerByRef -UserId $user.Id -BodyParameter @{ \"@odata.id\" = \"https://graph.microsoft.com/v1.0/users/$($manager.Id)\" }\n Write-Host \"Manager set successfully\" -ForegroundColor Green\n}",
|
|
"label": "Set manager"
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"id": "step_8",
|
|
"type": "procedure_step",
|
|
"title": "Configure MFA",
|
|
"content_type": "action",
|
|
"description": "Ensure MFA is configured for **[VAR:display_name]**.\n\nIf the tenant uses **Security Defaults** or **Conditional Access**, MFA will be prompted on first login.\n\nIf using per-user MFA, enable it manually:\n1. Go to Microsoft Entra admin center > Users > Per-user MFA\n2. Find **[VAR:email_address]**\n3. Set MFA status to **Enabled**\n\nThe user will be prompted to set up the Microsoft Authenticator app on first login.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "MFA configured or will be prompted on first sign-in?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_9",
|
|
"type": "procedure_step",
|
|
"title": "Send Welcome Email & Credentials",
|
|
"content_type": "informational",
|
|
"description": "Send the new user their login credentials securely.\n\n**Account details to communicate:**\n- Email: **[VAR:email_address]**\n- Temporary password: *(from step 2)*\n- Sign-in URL: https://portal.office.com\n- They will be prompted to change their password on first login\n- They will be prompted to set up MFA (Microsoft Authenticator app)\n\n**Best practice:** Send the username and password via separate channels (e.g., email the username to the manager, text the password to the user's personal phone).",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Welcome email/credentials sent securely?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_10",
|
|
"type": "procedure_step",
|
|
"title": "Document & Update Ticket",
|
|
"content_type": "informational",
|
|
"description": "Update the documentation and close the ticket.\n\n**Ticket: [VAR:ticket_number]**\n\n| Item | Value |\n|------|-------|\n| User | **[VAR:display_name]** |\n| Email | **[VAR:email_address]** |\n| License | **[VAR:license_type]** |\n| Department | **[VAR:department]** |\n| Title | **[VAR:job_title]** |\n| Start Date | **[VAR:start_date]** |\n| Groups | [VAR:security_groups] |\n| Shared Mailboxes | [VAR:shared_mailboxes] |",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Ticket updated and documented?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_end",
|
|
"type": "procedure_end",
|
|
"title": "User Onboarding Complete",
|
|
"description": "**[VAR:display_name]** ([VAR:email_address]) has been onboarded to Microsoft 365 for **[VAR:client_name]**.\n\n**Summary:**\n- Account created with **[VAR:license_type]**\n- Added to security groups\n- Shared mailbox access configured\n- MFA will be prompted on first sign-in\n\n**Follow-up in 24 hours:**\n- Verify user has logged in successfully\n- Confirm MFA is registered\n- Check shared mailboxes auto-mapped in Outlook",
|
|
},
|
|
]
|
|
},
|
|
}
|
|
|
|
|
|
def get_vpn_gateway_flow() -> dict[str, Any]:
|
|
"""Site-to-Site VPN Configuration — firewall/network setup procedure."""
|
|
return {
|
|
"name": "Site-to-Site VPN Configuration",
|
|
"description": "Step-by-step procedure for configuring a site-to-site IPSec VPN tunnel between two locations. Covers network planning, firewall configuration, tunnel setup, and verification.",
|
|
"tree_type": "procedural",
|
|
"category": "Projects - Networking",
|
|
"tags": ["vpn", "ipsec", "networking", "firewall"],
|
|
"intake_form": [
|
|
{
|
|
"variable_name": "site_a_name",
|
|
"label": "Site A Name (Primary)",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. HQ Office",
|
|
"group_name": "Site A — Primary",
|
|
"display_order": 1,
|
|
},
|
|
{
|
|
"variable_name": "site_a_public_ip",
|
|
"label": "Site A Public IP",
|
|
"field_type": "ip_address",
|
|
"required": True,
|
|
"placeholder": "e.g. 203.0.113.10",
|
|
"group_name": "Site A — Primary",
|
|
"display_order": 2,
|
|
},
|
|
{
|
|
"variable_name": "site_a_lan_subnet",
|
|
"label": "Site A LAN Subnet",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. 10.0.1.0/24",
|
|
"help_text": "Local network behind Site A's firewall",
|
|
"group_name": "Site A — Primary",
|
|
"display_order": 3,
|
|
},
|
|
{
|
|
"variable_name": "site_a_firewall",
|
|
"label": "Site A Firewall Model",
|
|
"field_type": "select",
|
|
"required": True,
|
|
"options": ["SonicWall", "Fortinet FortiGate", "Meraki MX", "pfSense/OPNsense", "Ubiquiti USG/UDM", "Other"],
|
|
"group_name": "Site A — Primary",
|
|
"display_order": 4,
|
|
},
|
|
{
|
|
"variable_name": "site_b_name",
|
|
"label": "Site B Name (Remote)",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Branch Office",
|
|
"group_name": "Site B — Remote",
|
|
"display_order": 5,
|
|
},
|
|
{
|
|
"variable_name": "site_b_public_ip",
|
|
"label": "Site B Public IP",
|
|
"field_type": "ip_address",
|
|
"required": True,
|
|
"placeholder": "e.g. 198.51.100.20",
|
|
"group_name": "Site B — Remote",
|
|
"display_order": 6,
|
|
},
|
|
{
|
|
"variable_name": "site_b_lan_subnet",
|
|
"label": "Site B LAN Subnet",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. 10.0.2.0/24",
|
|
"help_text": "Local network behind Site B's firewall",
|
|
"group_name": "Site B — Remote",
|
|
"display_order": 7,
|
|
},
|
|
{
|
|
"variable_name": "site_b_firewall",
|
|
"label": "Site B Firewall Model",
|
|
"field_type": "select",
|
|
"required": True,
|
|
"options": ["SonicWall", "Fortinet FortiGate", "Meraki MX", "pfSense/OPNsense", "Ubiquiti USG/UDM", "Other"],
|
|
"group_name": "Site B — Remote",
|
|
"display_order": 8,
|
|
},
|
|
{
|
|
"variable_name": "psk",
|
|
"label": "Pre-Shared Key",
|
|
"field_type": "password",
|
|
"required": True,
|
|
"help_text": "IKE pre-shared key — minimum 20 characters recommended. Store in password vault.",
|
|
"group_name": "Tunnel Configuration",
|
|
"display_order": 9,
|
|
},
|
|
{
|
|
"variable_name": "encryption",
|
|
"label": "Encryption Algorithm",
|
|
"field_type": "select",
|
|
"required": True,
|
|
"options": ["AES-256-GCM (recommended)", "AES-256-CBC", "AES-128-GCM", "AES-128-CBC"],
|
|
"default_value": "AES-256-GCM (recommended)",
|
|
"group_name": "Tunnel Configuration",
|
|
"display_order": 10,
|
|
},
|
|
{
|
|
"variable_name": "ike_version",
|
|
"label": "IKE Version",
|
|
"field_type": "select",
|
|
"required": True,
|
|
"options": ["IKEv2 (recommended)", "IKEv1"],
|
|
"default_value": "IKEv2 (recommended)",
|
|
"group_name": "Tunnel Configuration",
|
|
"display_order": 11,
|
|
},
|
|
{
|
|
"variable_name": "client_name",
|
|
"label": "Client Name",
|
|
"field_type": "text",
|
|
"required": True,
|
|
"placeholder": "e.g. Contoso Ltd",
|
|
"group_name": "Project Info",
|
|
"display_order": 12,
|
|
},
|
|
{
|
|
"variable_name": "ticket_number",
|
|
"label": "Ticket / Project Number",
|
|
"field_type": "text",
|
|
"required": False,
|
|
"placeholder": "e.g. PRJ-2024-0099",
|
|
"group_name": "Project Info",
|
|
"display_order": 13,
|
|
},
|
|
],
|
|
"tree_structure": {
|
|
"steps": [
|
|
{
|
|
"id": "step_1",
|
|
"type": "procedure_step",
|
|
"title": "Verify Network Prerequisites",
|
|
"content_type": "verification",
|
|
"description": "Verify the following before configuring the VPN for **[VAR:client_name]** (ticket: **[VAR:ticket_number]**):\n\n**Site A — [VAR:site_a_name]:**\n- Public IP: **[VAR:site_a_public_ip]** (static, not behind NAT if possible)\n- LAN Subnet: **[VAR:site_a_lan_subnet]**\n- Firewall: **[VAR:site_a_firewall]**\n\n**Site B — [VAR:site_b_name]:**\n- Public IP: **[VAR:site_b_public_ip]** (static, not behind NAT if possible)\n- LAN Subnet: **[VAR:site_b_lan_subnet]**\n- Firewall: **[VAR:site_b_firewall]**\n\n**Critical checks:**\n- LAN subnets must NOT overlap\n- Both firewalls support IPSec VPN\n- ISPs are not blocking UDP 500 and 4500\n- Both sites have admin access to their firewall",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "All prerequisites verified — no subnet overlap, admin access confirmed?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_2",
|
|
"type": "procedure_step",
|
|
"title": "Document the Tunnel Configuration",
|
|
"content_type": "informational",
|
|
"description": "Record the agreed tunnel parameters before configuring either side:\n\n| Parameter | Value |\n|-----------|-------|\n| IKE Version | **[VAR:ike_version]** |\n| Encryption | **[VAR:encryption]** |\n| Hash | SHA-256 |\n| DH Group | 14 (2048-bit) |\n| SA Lifetime | Phase 1: 28800s / Phase 2: 3600s |\n| PFS | Enabled (DH Group 14) |\n| Site A Peer | **[VAR:site_a_public_ip]** |\n| Site B Peer | **[VAR:site_b_public_ip]** |\n| Site A Network | **[VAR:site_a_lan_subnet]** |\n| Site B Network | **[VAR:site_b_lan_subnet]** |\n\n**Both sides MUST match exactly.** Save this table — you'll reference it during configuration.",
|
|
},
|
|
{
|
|
"id": "step_3",
|
|
"type": "procedure_step",
|
|
"title": "Configure Site A Firewall",
|
|
"content_type": "action",
|
|
"description": "Configure the VPN tunnel on **[VAR:site_a_name]** (**[VAR:site_a_firewall]**).\n\nLog into the firewall at **[VAR:site_a_name]** and create a new site-to-site VPN policy:\n\n1. **Phase 1 (IKE SA):**\n - Remote gateway: **[VAR:site_b_public_ip]**\n - Authentication: Pre-shared key\n - IKE version: **[VAR:ike_version]**\n - Encryption: **[VAR:encryption]**\n - Hash: SHA-256\n - DH Group: 14\n - Lifetime: 28800 seconds\n\n2. **Phase 2 (IPSec SA):**\n - Local network: **[VAR:site_a_lan_subnet]**\n - Remote network: **[VAR:site_b_lan_subnet]**\n - Encryption: **[VAR:encryption]**\n - Hash: SHA-256\n - PFS: Group 14\n - Lifetime: 3600 seconds\n\n3. **Firewall rules:**\n - Allow traffic from **[VAR:site_a_lan_subnet]** to **[VAR:site_b_lan_subnet]** via VPN zone\n - Allow traffic from **[VAR:site_b_lan_subnet]** to **[VAR:site_a_lan_subnet]** via VPN zone",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Site A VPN policy and firewall rules configured?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_4",
|
|
"type": "procedure_step",
|
|
"title": "Configure Site B Firewall",
|
|
"content_type": "action",
|
|
"description": "Configure the matching VPN tunnel on **[VAR:site_b_name]** (**[VAR:site_b_firewall]**).\n\nRepeat the same configuration, but mirror the local/remote settings:\n\n1. **Phase 1 (IKE SA):**\n - Remote gateway: **[VAR:site_a_public_ip]**\n - All other settings IDENTICAL to Site A\n\n2. **Phase 2 (IPSec SA):**\n - Local network: **[VAR:site_b_lan_subnet]**\n - Remote network: **[VAR:site_a_lan_subnet]**\n - All other settings IDENTICAL to Site A\n\n3. **Firewall rules:**\n - Allow traffic from **[VAR:site_b_lan_subnet]** to **[VAR:site_a_lan_subnet]** via VPN zone\n - Allow traffic from **[VAR:site_a_lan_subnet]** to **[VAR:site_b_lan_subnet]** via VPN zone\n\n**Double check:** Encryption, hash, DH group, PFS, and lifetimes must be identical on both sides.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Site B VPN policy and firewall rules configured (matching Site A)?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_5",
|
|
"type": "procedure_step",
|
|
"title": "Initiate Tunnel & Verify Phase 1",
|
|
"content_type": "verification",
|
|
"description": "Bring up the VPN tunnel and verify Phase 1 (IKE) negotiation succeeds.\n\nOn most firewalls, you can force the tunnel to initiate by:\n- Sending a ping from one LAN to the other\n- Using the firewall's \"Connect\" or \"Bring Up\" button in the VPN status page\n\n**Check the VPN status page on both firewalls:**\n- Phase 1 should show **Established** or **Connected**\n- If Phase 1 fails, check: pre-shared key match, IKE version match, encryption/hash match, public IPs correct\n\n**Common Phase 1 failures:**\n- Mismatched PSK (most common)\n- ISP blocking UDP 500/4500\n- NAT-T issues (enable NAT traversal if behind NAT)\n- DH group mismatch",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Phase 1 (IKE) established on both firewalls?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_6",
|
|
"type": "procedure_step",
|
|
"title": "Verify Phase 2 & Traffic Flow",
|
|
"content_type": "verification",
|
|
"description": "Verify Phase 2 (IPSec) is established and traffic flows between sites.\n\n**From a device at [VAR:site_a_name] ([VAR:site_a_lan_subnet]):**\n- Ping a device at [VAR:site_b_name] ([VAR:site_b_lan_subnet])\n- Try accessing a file share or RDP session across the tunnel\n\n**From a device at [VAR:site_b_name] ([VAR:site_b_lan_subnet]):**\n- Ping a device at [VAR:site_a_name] ([VAR:site_a_lan_subnet])\n\n**Common Phase 2 failures:**\n- Mismatched encryption/hash/PFS settings\n- Incorrect local/remote network definitions (proxy IDs)\n- Firewall rules not allowing VPN zone traffic\n- Asymmetric routing (traffic going out default route instead of tunnel)",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Bidirectional traffic confirmed between sites?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_7",
|
|
"type": "procedure_step",
|
|
"title": "Test Critical Services",
|
|
"content_type": "verification",
|
|
"description": "Test the specific services that need to work across the VPN:\n\n- **File shares:** Access shared folders from remote site\n- **RDP:** Remote desktop to servers across the tunnel\n- **Active Directory:** If domain-joined, verify domain auth works from remote site\n- **DNS:** Resolve internal hostnames across the tunnel\n- **Printing:** If printers need cross-site access\n\nTest from both directions (A→B and B→A).",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "All required services working bidirectionally?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_8",
|
|
"type": "procedure_step",
|
|
"title": "Configure Monitoring & Alerts",
|
|
"content_type": "action",
|
|
"description": "Set up monitoring to detect VPN tunnel drops:\n\n1. **On the firewalls:** Enable VPN tunnel status alerting (email/SNMP)\n2. **In your RMM/monitoring tool:** Add a ping monitor from each site to a host on the other site\n3. **Recommended thresholds:**\n - Alert if tunnel is down for > 5 minutes\n - Alert if latency exceeds 100ms\n - Alert if packet loss exceeds 1%\n\n**DPD (Dead Peer Detection):**\nEnsure DPD is enabled on both firewalls with a 10-second interval. This allows automatic tunnel renegotiation after brief outages.",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Monitoring and DPD configured?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_9",
|
|
"type": "procedure_step",
|
|
"title": "Document & Update Password Vault",
|
|
"content_type": "informational",
|
|
"description": "Document the complete VPN configuration:\n\n| Parameter | Site A | Site B |\n|-----------|--------|--------|\n| Name | **[VAR:site_a_name]** | **[VAR:site_b_name]** |\n| Public IP | **[VAR:site_a_public_ip]** | **[VAR:site_b_public_ip]** |\n| LAN Subnet | **[VAR:site_a_lan_subnet]** | **[VAR:site_b_lan_subnet]** |\n| Firewall | **[VAR:site_a_firewall]** | **[VAR:site_b_firewall]** |\n| IKE Version | **[VAR:ike_version]** | **[VAR:ike_version]** |\n| Encryption | **[VAR:encryption]** | **[VAR:encryption]** |\n\n**Store in password vault:**\n- Pre-shared key\n- Firewall admin credentials for both sites\n\n**Update:**\n- Client network diagram with VPN tunnel\n- Firewall configuration backup on both sites",
|
|
"verification": {
|
|
"type": "checkbox",
|
|
"prompt": "Documentation complete and PSK stored in vault?"
|
|
},
|
|
},
|
|
{
|
|
"id": "step_end",
|
|
"type": "procedure_end",
|
|
"title": "VPN Tunnel Active",
|
|
"description": "Site-to-site VPN tunnel is established between **[VAR:site_a_name]** and **[VAR:site_b_name]** for **[VAR:client_name]**.\n\n**Summary:**\n- Tunnel: **[VAR:site_a_public_ip]** ↔ **[VAR:site_b_public_ip]**\n- Networks: **[VAR:site_a_lan_subnet]** ↔ **[VAR:site_b_lan_subnet]**\n- Encryption: **[VAR:encryption]** / **[VAR:ike_version]**\n\n**Follow-up:**\n- Verify tunnel stability over 24-48 hours\n- Confirm monitoring alerts fire correctly (test with a brief tunnel disconnect)\n- Schedule quarterly PSK rotation",
|
|
},
|
|
]
|
|
},
|
|
}
|
|
|
|
|
|
# =============================================================================
|
|
# 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_procedural_flow(client: httpx.AsyncClient, token: str, flow_data: dict) -> dict | None:
|
|
"""Create a procedural flow via the API. Returns None if it already exists."""
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
|
|
# Mark as default/system flow (public and visible to all)
|
|
flow_data["is_default"] = True
|
|
flow_data["is_public"] = True
|
|
|
|
# Check if flow with same name exists
|
|
list_response = await client.get(f"{API_BASE_URL}/trees", headers=headers, params={"tree_type": "procedural"})
|
|
if list_response.status_code == 200:
|
|
existing = list_response.json()
|
|
for tree in existing:
|
|
if tree["name"] == flow_data["name"]:
|
|
if not tree.get("is_public") or not tree.get("is_default"):
|
|
patch_response = await client.put(
|
|
f"{API_BASE_URL}/trees/{tree['id']}",
|
|
json={"is_public": True, "is_default": True},
|
|
headers=headers,
|
|
)
|
|
if patch_response.status_code == 200:
|
|
print(f" [UPDATE] Flow '{flow_data['name']}' visibility updated (ID: {tree['id']})")
|
|
return None
|
|
print(f" [SKIP] Flow '{flow_data['name']}' already exists (ID: {tree['id']})")
|
|
return None
|
|
|
|
# Create the flow
|
|
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_procedural_flows():
|
|
"""Main seeding function."""
|
|
print("\n" + "=" * 60)
|
|
print(" PATHERLY - Procedural Flow Templates Seeder")
|
|
print("=" * 60)
|
|
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
# Health check
|
|
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
|
|
|
|
# Authenticate
|
|
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
|
|
|
|
# Get flow definitions
|
|
print("\n[2/3] Preparing procedural flows...")
|
|
flows_to_create = [
|
|
("Projects - Infrastructure", get_domain_controller_flow()),
|
|
("Projects - Microsoft 365", get_m365_user_onboarding_flow()),
|
|
("Projects - Networking", get_vpn_gateway_flow()),
|
|
]
|
|
print(f" Found {len(flows_to_create)} flows to seed\n")
|
|
|
|
# Create flows
|
|
print("[3/3] Creating procedural flows...")
|
|
created_count = 0
|
|
skipped_count = 0
|
|
|
|
current_category = None
|
|
for category, flow_data in flows_to_create:
|
|
if category != current_category:
|
|
print(f"\n {category}:")
|
|
current_category = category
|
|
|
|
try:
|
|
result = await create_procedural_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}")
|
|
|
|
# Summary
|
|
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 procedural 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 (default: http://localhost:8000/api/v1)")
|
|
args = parser.parse_args()
|
|
|
|
ADMIN_EMAIL = args.email
|
|
ADMIN_PASSWORD = args.password
|
|
API_BASE_URL = args.api_url
|
|
|
|
asyncio.run(seed_procedural_flows())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|