Public/Network/New-VergeIPSecConnection.ps1

function New-VergeIPSecConnection {
    <#
    .SYNOPSIS
        Creates a new IPSec VPN connection on a VergeOS network.

    .DESCRIPTION
        New-VergeIPSecConnection creates an IPSec Phase 1 (IKE) VPN connection.
        This establishes the IKE security association with a remote gateway.

    .PARAMETER Network
        The name or key of the network to create the connection on.

    .PARAMETER NetworkObject
        A network object from Get-VergeNetwork. Accepts pipeline input.

    .PARAMETER Name
        A unique name for the IPSec connection.

    .PARAMETER RemoteGateway
        The IP address or hostname of the remote VPN gateway.

    .PARAMETER PreSharedKey
        The pre-shared key for authentication. Required for PSK auth method.

    .PARAMETER KeyExchange
        The IKE version to use: IKEv1, IKEv2, or Auto (default).
        Auto uses IKEv2 when initiating and accepts either as responder.

    .PARAMETER Encryption
        The IKE encryption algorithms to use.
        Default: "aes256-sha256-modp2048"

    .PARAMETER IKELifetime
        Lifetime of the IKE SA in seconds. Default: 10800 (3 hours).

    .PARAMETER ConnectionMode
        How the connection should behave:
        - ResponderOnly: Load but don't initiate
        - OnDemand: Start when traffic is detected (default)
        - Start: Initiate immediately on startup

    .PARAMETER Identifier
        Local identifier for the connection. Defaults to local IP.

    .PARAMETER PeerIdentifier
        Remote peer identifier. Defaults to remote gateway.

    .PARAMETER Negotiation
        IKEv1 negotiation mode: Main (default) or Aggressive.

    .PARAMETER DPDAction
        Dead Peer Detection action: Disabled, Clear, Hold, or Restart (default).

    .PARAMETER DPDDelay
        Interval between DPD messages in seconds. Default: 30.

    .PARAMETER ForceUDPEncap
        Force UDP encapsulation even without NAT.

    .PARAMETER MOBIKE
        Enable IKEv2 MOBIKE protocol for mobility.

    .PARAMETER Description
        Optional description for the connection.

    .PARAMETER Enabled
        Whether the connection is enabled. Default: true.

    .PARAMETER PassThru
        Return the created connection object.

    .PARAMETER Server
        The VergeOS connection to use.

    .EXAMPLE
        New-VergeIPSecConnection -Network "External" -Name "Site-B" -RemoteGateway "203.0.113.1" -PreSharedKey "MySecretKey123"

        Creates a basic site-to-site VPN connection.

    .EXAMPLE
        $params = @{
            Network = "External"
            Name = "Azure-VPN"
            RemoteGateway = "azure-vpn.eastus.cloudapp.net"
            PreSharedKey = "ComplexKey!@#"
            KeyExchange = "IKEv2"
            Encryption = "aes256gcm16-sha384-modp2048"
            ConnectionMode = "Start"
        }
        New-VergeIPSecConnection @params -PassThru

        Creates an IKEv2 connection with custom encryption.

    .OUTPUTS
        None by default. Verge.IPSecConnection when -PassThru is specified.

    .NOTES
        After creating a connection, you need to add Phase 2 policies with
        New-VergeIPSecPolicy to define which traffic should traverse the tunnel.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ByNetworkName')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByNetworkName')]
        [string]$Network,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByNetworkObject')]
        [PSTypeName('Verge.Network')]
        [PSCustomObject]$NetworkObject,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateLength(1, 128)]
        [string]$Name,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$RemoteGateway,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$PreSharedKey,

        [Parameter()]
        [ValidateSet('Auto', 'IKEv1', 'IKEv2')]
        [string]$KeyExchange = 'Auto',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Encryption = 'aes256-sha256-modp2048',

        [Parameter()]
        [ValidateRange(60, 86400)]
        [int]$IKELifetime = 10800,

        [Parameter()]
        [ValidateSet('ResponderOnly', 'OnDemand', 'Start')]
        [string]$ConnectionMode = 'OnDemand',

        [Parameter()]
        [string]$Identifier,

        [Parameter()]
        [string]$PeerIdentifier,

        [Parameter()]
        [ValidateSet('Main', 'Aggressive')]
        [string]$Negotiation = 'Main',

        [Parameter()]
        [ValidateSet('Disabled', 'Clear', 'Hold', 'Restart')]
        [string]$DPDAction = 'Restart',

        [Parameter()]
        [ValidateRange(0, 3600)]
        [int]$DPDDelay = 30,

        [Parameter()]
        [switch]$ForceUDPEncap,

        [Parameter()]
        [switch]$MOBIKE,

        [Parameter()]
        [string]$Description,

        [Parameter()]
        [bool]$Enabled = $true,

        [Parameter()]
        [switch]$PassThru,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }

        # Map friendly names to API values
        $keyExchangeApiMap = @{
            'Auto'  = 'ike'
            'IKEv1' = 'ikev1'
            'IKEv2' = 'ikev2'
        }

        $connectionModeApiMap = @{
            'ResponderOnly' = 'add'
            'OnDemand'      = 'route'
            'Start'         = 'start'
        }

        $dpdActionApiMap = @{
            'Disabled' = 'none'
            'Clear'    = 'clear'
            'Hold'     = 'hold'
            'Restart'  = 'restart'
        }
    }

    process {
        # Resolve network
        $targetNetwork = $null
        if ($PSCmdlet.ParameterSetName -eq 'ByNetworkObject') {
            $targetNetwork = $NetworkObject
        }
        else {
            if ($Network -match '^\d+$') {
                $targetNetwork = Get-VergeNetwork -Key ([int]$Network) -Server $Server
            }
            else {
                $targetNetwork = Get-VergeNetwork -Name $Network -Server $Server
            }
        }

        if (-not $targetNetwork) {
            Write-Error -Message "Network '$Network' not found" -ErrorId 'NetworkNotFound'
            return
        }

        # Get or create IPSec config for this network
        $ipsecQuery = @{
            filter = "vnet eq $($targetNetwork.Key)"
            fields = '$key'
        }

        $ipsecConfig = Invoke-VergeAPI -Method GET -Endpoint 'vnet_ipsecs' -Query $ipsecQuery -Connection $Server

        $ipsecKey = $null
        if ($ipsecConfig -and ($ipsecConfig.'$key' -or ($ipsecConfig -is [array] -and $ipsecConfig.Count -gt 0))) {
            $ipsecKey = if ($ipsecConfig -is [array]) { $ipsecConfig[0].'$key' } else { $ipsecConfig.'$key' }
        }

        if (-not $ipsecKey) {
            # Create IPSec config for this network
            Write-Verbose "Creating IPSec configuration for network '$($targetNetwork.Name)'"
            $ipsecBody = @{
                vnet    = $targetNetwork.Key
                enabled = $true
                mode    = 'normal'
            }

            $createResponse = Invoke-VergeAPI -Method POST -Endpoint 'vnet_ipsecs' -Body $ipsecBody -Connection $Server
            $ipsecKey = $createResponse.'$key'

            if (-not $ipsecKey) {
                Write-Error -Message "Failed to create IPSec configuration for network" -ErrorId 'IPSecConfigCreateFailed'
                return
            }
        }

        # Build Phase 1 body
        $body = @{
            ipsec          = $ipsecKey
            enabled        = $Enabled
            name           = $Name
            remote_gateway = $RemoteGateway
            auth           = 'psk'
            psk            = $PreSharedKey
            keyexchange    = $keyExchangeApiMap[$KeyExchange]
            ike            = $Encryption
            ikelifetime    = $IKELifetime
            auto           = $connectionModeApiMap[$ConnectionMode]
            negotiation    = $Negotiation.ToLower()
            dpdaction      = $dpdActionApiMap[$DPDAction]
            dpddelay       = $DPDDelay
            forceencaps    = $ForceUDPEncap.IsPresent
            mobike         = $MOBIKE.IsPresent
        }

        if ($Identifier) {
            $body['identifier'] = $Identifier
        }

        if ($PeerIdentifier) {
            $body['peer_identifier'] = $PeerIdentifier
        }

        if ($Description) {
            $body['description'] = $Description
        }

        if ($PSCmdlet.ShouldProcess("$Name to $RemoteGateway on $($targetNetwork.Name)", "Create IPSec Connection")) {
            try {
                Write-Verbose "Creating IPSec connection '$Name' to '$RemoteGateway'"
                $response = Invoke-VergeAPI -Method POST -Endpoint 'vnet_ipsec_phase1s' -Body $body -Connection $Server

                $connKey = $response.'$key'
                if (-not $connKey -and $response.key) {
                    $connKey = $response.key
                }

                Write-Verbose "IPSec connection created with Key: $connKey"

                if ($PassThru -and $connKey) {
                    Start-Sleep -Milliseconds 500
                    Get-VergeIPSecConnection -Network $targetNetwork.Key -Key $connKey -Server $Server
                }
            }
            catch {
                throw "Failed to create IPSec connection: $($_.Exception.Message)"
            }
        }
    }
}