From cb3b6ebe89c5ce860ff94e3983c077a8927e4340 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Fri, 31 Oct 2025 16:27:17 +0100 Subject: [PATCH 01/12] Connection: Fix indent... --- PowerFGT/Public/Connection.ps1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PowerFGT/Public/Connection.ps1 b/PowerFGT/Public/Connection.ps1 index efb3c62b..cfa9c87f 100644 --- a/PowerFGT/Public/Connection.ps1 +++ b/PowerFGT/Public/Connection.ps1 @@ -210,8 +210,9 @@ function Connect-FGT { if ( $iwrResponse.Content[0] -eq "{") { $json = $iwrResponse.Content | ConvertFrom-Json $status = $json.status - } else { - $status = $iwrResponse.Content[0] + } + else { + $status = $iwrResponse.Content[0] } #check if need token... From bce5e93fe842cbe43e5962ba80c55639af617bf8 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Fri, 31 Oct 2025 16:24:58 +0100 Subject: [PATCH 02/12] Connection: use api/v2/authentication for login Need to use this API call with last release of FortiOS (7.6.4 or 8.0 ...) you can use always old method using -oldauth parameter --- PowerFGT/Public/Connection.ps1 | 75 ++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/PowerFGT/Public/Connection.ps1 b/PowerFGT/Public/Connection.ps1 index cfa9c87f..4bedc73b 100644 --- a/PowerFGT/Public/Connection.ps1 +++ b/PowerFGT/Public/Connection.ps1 @@ -127,6 +127,8 @@ function Connect-FGT { [Parameter(Mandatory = $false)] [string]$license, [Parameter(Mandatory = $false)] + [switch]$oldauth = $false, + [Parameter(Mandatory = $false)] [string[]]$vdom, [Parameter(Mandatory = $false)] [boolean]$DefaultConnection = $true @@ -186,6 +188,79 @@ function Connect-FGT { if ($ApiToken) { $headers += @{ "Authorization" = "Bearer $ApiToken" } } + elseif ( $oldauth -ne $true ) { + #If there is a password (and a user), create a credentials + if ($Password) { + $Credentials = New-Object System.Management.Automation.PSCredential($Username, $Password) + } + #Not Credentials (and no password) + if ($null -eq $Credentials) { + $Credentials = Get-Credential -Message 'Please enter administrative credentials for your FortiGate' + } + # new api auth method (Seen FortiOS 6.4+) + $postParams = @{ + username = $Credentials.username; + password = $Credentials.GetNetworkCredential().Password; + secretkey = $Credentials.GetNetworkCredential().Password; + } + + $uri = $url + "api/v2/authentication" + Write-Verbose $uri + + try { + $irmResponse = Invoke-RestMethod $uri -Method POST -Body ($postParams | ConvertTo-Json) -SessionVariable FGT @invokeParams + } + catch { + Show-FGTException $_ + throw "Unable to connect to FortiGate" + } + + Write-verbose $irmResponse + #Following FortiOS version it is status_code or status... + if ($irmResponse.status_code) { + $status = $irmResponse.status_code + } + else { + $status = $irmResponse.status + } + + switch ($status) { + '-1' { + throw "Log in failure. Most likely an incorrect username/password combo" + } + '-4' { + #with FortiOS 7.6.3+, no longer warning when locked account... + throw "Admin is now locked out (Please retry in 60 seconds)" + } + '5' { + #no thing, it is good ! continue + } + '6' { + #no thing, it is good ! continue (with FortiOS 7.6.3+) + } + default { + throw "Authentication failure. Status code: $status $($irmResponse.status_message)" + } + } + + #Search crsf cookie and to X-CSRFTOKEN + $cookies = $FGT.Cookies.GetCookies($uri) + foreach ($cookie in $cookies) { + if ($cookie.name -like "ccsrftoken*") { + $cookie_csrf = $cookie.value + } + } + + # throw if don't found csrf cookie... + if ($null -eq $cookie_csrf) { + throw "Unable to found CSRF Cookie" + } + + #Remove extra "quote" + $cookie_csrf = $cookie_csrf -replace '["]', '' + #Add csrf cookie to header (X-CSRFTOKEN) + $headers += @{"X-CSRFTOKEN" = $cookie_csrf } + } else { #If there is a password (and a user), create a credentials if ($Password) { From b0e91624e243b75ad9752cfeeaa2b0b172cd200a Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Fri, 31 Oct 2025 16:44:50 +0100 Subject: [PATCH 03/12] Connection: fix ccsrf token with FortiOS 7.6.3+ --- PowerFGT/Public/Connection.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PowerFGT/Public/Connection.ps1 b/PowerFGT/Public/Connection.ps1 index 4bedc73b..2bd86579 100644 --- a/PowerFGT/Public/Connection.ps1 +++ b/PowerFGT/Public/Connection.ps1 @@ -246,7 +246,8 @@ function Connect-FGT { #Search crsf cookie and to X-CSRFTOKEN $cookies = $FGT.Cookies.GetCookies($uri) foreach ($cookie in $cookies) { - if ($cookie.name -like "ccsrftoken*") { + if ($cookie.name -like "ccsrf*" ) { + #before 7.6 it was ccsrftoken_port_xxxx, from 7.6.3+ it is ccsrf_token_port_xxxx $cookie_csrf = $cookie.value } } From 363536dfd22aeccbaeb9cb05fc273458e1e56075 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Fri, 31 Oct 2025 21:26:05 +0100 Subject: [PATCH 04/12] Tests: Add oldauth variable for CI --- Tests/common.ps1 | 1 + Tests/credential.ci.ps1 | 1 + Tests/credential.example.ps1 | 1 + 3 files changed, 3 insertions(+) diff --git a/Tests/common.ps1 b/Tests/common.ps1 index 9f2cbd87..982d24c6 100644 --- a/Tests/common.ps1 +++ b/Tests/common.ps1 @@ -100,6 +100,7 @@ else { $invokeParams.add('SkipCertificateCheck', $SkipCertificateCheck) $invokeParams.add('httpOnly', $httpOnly) $invokeParams.add('port', $port) +$invokeParams.add('oldauth', $oldauth) #Make a connection for check info and store version (used for some test...) $fgt = Connect-FGT @invokeParams diff --git a/Tests/credential.ci.ps1 b/Tests/credential.ci.ps1 index ba3779b9..70d8473e 100644 --- a/Tests/credential.ci.ps1 +++ b/Tests/credential.ci.ps1 @@ -9,6 +9,7 @@ $script:ipaddress = $env:IPADDRESS $script:login = $env:LOGIN $script:password = $env:PASSWORD +$script:oldauth = $env:OLDAUTH $script:httpOnly = $false $script:SkipCertificateCheck = $true $script:ci = $true diff --git a/Tests/credential.example.ps1 b/Tests/credential.example.ps1 index f3ed7e9f..c9c776ea 100644 --- a/Tests/credential.example.ps1 +++ b/Tests/credential.example.ps1 @@ -14,6 +14,7 @@ $script:httpOnly = $true #$script:apitoken = "yourtoken" $script:SkipCertificateCheck = $true $script:ci = $false +$script:oldauth = $false #default settings use for test, can be override if needed... $script:pester_address1 = "pester_address1" From 1ee6f6012947fc1f1b212bfe07fbb03063fa00dc Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Fri, 31 Oct 2025 21:29:43 +0100 Subject: [PATCH 05/12] azure-pipeline: Add OLDAUTH variable --- Tests/common.ps1 | 6 ++++++ azure-pipelines.yml | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/Tests/common.ps1 b/Tests/common.ps1 index 982d24c6..11a01818 100644 --- a/Tests/common.ps1 +++ b/Tests/common.ps1 @@ -100,6 +100,12 @@ else { $invokeParams.add('SkipCertificateCheck', $SkipCertificateCheck) $invokeParams.add('httpOnly', $httpOnly) $invokeParams.add('port', $port) +if ($oldauth -eq "true") { + $oldauth = $true +} +else { + $oldauth = $false +} $invokeParams.add('oldauth', $oldauth) #Make a connection for check info and store version (used for some test...) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9fddd0dc..f423be57 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,24 +23,31 @@ stages: FortiOS60: name: "FortiOS 6.0" IPADDRESS: $(IPADDRESS_60) + OLDAUTH: true FortiOS62: name: "FortiOS 6.2" IPADDRESS: $(IPADDRESS_62) + OLDAUTH: true FortiOS64: name: "FortiOS 6.4" IPADDRESS: $(IPADDRESS_64) + OLDAUTH: false FortiOS70: name: "FortiOS 7.0" IPADDRESS: $(IPADDRESS_70) + OLDAUTH: false FortiOS72: name: "FortiOS 7.2" IPADDRESS: $(IPADDRESS_72) + OLDAUTH: false FortiOS74: name: "FortiOS 7.4" IPADDRESS: $(IPADDRESS_74) + OLDAUTH: false FortiOS76: name: "FortiOS 7.6" IPADDRESS: $(IPADDRESS_76) + OLDAUTH: false steps: - task: PowerShell@2 displayName: "Test PowerShell Core $(name)" @@ -62,6 +69,7 @@ stages: LOGIN: $(LOGIN) IPADDRESS: $(IPADDRESS) PASSWORD: $(PASSWORD) + OLDAUTH: $(OLDAUTH) - task: PublishTestResults@2 inputs: testResultsFormat: "NUnit" @@ -118,6 +126,7 @@ stages: LOGIN: $(LOGIN) IPADDRESS: $(IPADDRESS) PASSWORD: $(PASSWORD) + OLDAUTH: $(OLDAUTH) - task: PowerShell@2 displayName: "Test PowerShell 5 $(name)" inputs: From 53927fa016e6929bf596d6650ce45f7bea3cc12c Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Sat, 1 Nov 2025 15:17:15 +0100 Subject: [PATCH 06/12] Connection(Disconnect): For Disconnect use method DELETE on uri and kept also support of oldauth with POST on logout uri --- PowerFGT/Public/Connection.ps1 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/PowerFGT/Public/Connection.ps1 b/PowerFGT/Public/Connection.ps1 index 2bd86579..e2e287fa 100644 --- a/PowerFGT/Public/Connection.ps1 +++ b/PowerFGT/Public/Connection.ps1 @@ -427,6 +427,7 @@ function Connect-FGT { $connection.invokeParams = $invokeParams $connection.vdom = $vdom $connection.serial = $version.serial + $connection.oldauth = $oldauth if ($version.results.current.major) { $connection.version = [version]"$($version.results.current.major).$($version.results.current.minor).$($version.results.current.patch)" } @@ -523,10 +524,17 @@ function Disconnect-FGT { Process { - $url = "logout" + $method = "DELETE" + $url = "api/v2/authentication" + + #old auth use logout url + if ($connection.oldauth) { + $url = "logout" + $method = "POST" + } if ($PSCmdlet.ShouldProcess($connection.server, 'Proceed with removal of FortiGate connection ?')) { - $null = invoke-FGTRestMethod -method "POST" -uri $url -connection $connection + $null = invoke-FGTRestMethod -method $method -uri $url -connection $connection if (Test-Path variable:global:DefaultFGTConnection) { Remove-Variable -name DefaultFGTConnection -scope global } From 24570df3159c424d3514979260a191e429622415 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Sat, 1 Nov 2025 15:31:50 +0100 Subject: [PATCH 07/12] Connection(Tests): add oldauth for Connection tests with old release --- Tests/integration/Connection.Tests.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/integration/Connection.Tests.ps1 b/Tests/integration/Connection.Tests.ps1 index 99f8b41c..536a7f76 100644 --- a/Tests/integration/Connection.Tests.ps1 +++ b/Tests/integration/Connection.Tests.ps1 @@ -7,7 +7,7 @@ Describe "Connect to a FortiGate (using HTTP)" { It "Connect to a FortiGate (using HTTP) and check global variable" -Skip:( -not $httpOnly ) { - Connect-FGT $ipaddress -Username $login -password $mysecpassword -httpOnly -port $port + Connect-FGT $ipaddress -Username $login -password $mysecpassword -httpOnly -port $port -oldauth:$oldauth $DefaultFGTConnection | Should -Not -BeNullOrEmpty $DefaultFGTConnection.server | Should -Be $ipaddress $DefaultFGTConnection.invokeParams | Should -Not -BeNullOrEmpty @@ -23,11 +23,11 @@ Describe "Connect to a FortiGate (using HTTP)" { $DefaultFGTConnection | Should -Be $null } It "Connect to a FortiGate (using HTTP) with wrong password" -Skip:( -not $httpOnly ) { - { Connect-FGT $ipaddress -Username $login -password $mywrongpassword -httpOnly -port $port } | Should -throw "Log in failure. Most likely an incorrect username/password combo" + { Connect-FGT $ipaddress -Username $login -password $mywrongpassword -httpOnly -port $port -oldauth $oldauth } | Should -throw "Log in failure. Most likely an incorrect username/password combo" } #TODO: Connect using MFA (token) and/or need to change password (admin expiration) It "Connect to a Fortigate (using HTTP) with apiToken" -Skip:( -not ($apitoken -ne $null -and $httpOnly -eq $true -and $fgt_version -ge "7.0.0")) { - Connect-FGT -Server $ipaddress -ApiToken $apitoken -port $port -httpOnly + Connect-FGT -Server $ipaddress -ApiToken $apitoken -port $port -httpOnly -oldauth:$oldauth $DefaultFGTConnection | Should -Not -BeNullOrEmpty $DefaultFGTConnection.server | Should -Be $ipaddress $DefaultFGTConnection.invokeParams | Should -Not -BeNullOrEmpty @@ -42,7 +42,7 @@ Describe "Connect to a FortiGate (using HTTP)" { Describe "Connect to a fortigate (using HTTPS)" { It "Connect to a FortiGate (using HTTPS and -SkipCertificateCheck) and check global variable" -Skip:($httpOnly) { - Connect-FGT $ipaddress -Username $login -password $mysecpassword -SkipCertificateCheck -port $port + Connect-FGT $ipaddress -Username $login -password $mysecpassword -SkipCertificateCheck -port $port -oldauth:$oldauth $DefaultFGTConnection | Should -Not -BeNullOrEmpty $DefaultFGTConnection.server | Should -Be $ipaddress $DefaultFGTConnection.invokeParams | Should -Not -BeNullOrEmpty @@ -60,11 +60,11 @@ Describe "Connect to a fortigate (using HTTPS)" { #This test only work with PowerShell 6 / Core (-SkipCertificateCheck don't change global variable but only Invoke-WebRequest/RestMethod) #This test will be fail, if there is valid certificate... It "Connect to a FortiGate (using HTTPS) and check global variable" -Skip:("Desktop" -eq $PSEdition -Or $httpOnly) { - { Connect-FGT $ipaddress -Username $login -password $mysecpassword } | Should -throw "Unable to connect (certificate)" + { Connect-FGT $ipaddress -Username $login -password $mysecpassword -oldauth:$oldauth } | Should -throw "Unable to connect (certificate)" } It "Connect to a FortiGate (using HTTPS) with wrong password" -Skip:($httpOnly) { - { Connect-FGT $ipaddress -Username $login -password $mywrongpassword -port $port -SkipCertificateCheck:$SkipCertificateCheck } | Should -throw "Log in failure. Most likely an incorrect username/password combo" + { Connect-FGT $ipaddress -Username $login -password $mywrongpassword -port $port -SkipCertificateCheck:$SkipCertificateCheck -oldauth:$oldauth } | Should -throw "Log in failure. Most likely an incorrect username/password combo" } It "Connect to a Fortigate (using HTTPS) with apiToken" -Skip:($apitoken -eq $null -Or $httpOnly) { From dc5b2b5f1749ff4d429f2be8c2137799b6f1cdf5 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Sat, 1 Nov 2025 22:07:38 +0100 Subject: [PATCH 08/12] common(tests): set oldauth to script variable --- Tests/common.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/common.ps1 b/Tests/common.ps1 index 11a01818..aad096c0 100644 --- a/Tests/common.ps1 +++ b/Tests/common.ps1 @@ -101,10 +101,10 @@ $invokeParams.add('SkipCertificateCheck', $SkipCertificateCheck) $invokeParams.add('httpOnly', $httpOnly) $invokeParams.add('port', $port) if ($oldauth -eq "true") { - $oldauth = $true + $script:oldauth = $true } else { - $oldauth = $false + $script:oldauth = $false } $invokeParams.add('oldauth', $oldauth) From 12307bdcde2e256496aa2b5aeffd2af0dddc4771 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Mon, 3 Nov 2025 07:16:44 +0100 Subject: [PATCH 09/12] azure-pipeline: Add oldauth variable for windows too --- azure-pipelines.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f423be57..098ed22c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -87,24 +87,31 @@ stages: FortiOS60: name: "FortiOS 6.0" IPADDRESS: $(IPADDRESS_60) + OLDAUTH: true FortiOS62: name: "FortiOS 6.2" IPADDRESS: $(IPADDRESS_62) + OLDAUTH: true FortiOS64: name: "FortiOS 6.4" IPADDRESS: $(IPADDRESS_64) + OLDAUTH: false FortiOS70: name: "FortiOS 7.0" IPADDRESS: $(IPADDRESS_70) + OLDAUTH: false FortiOS72: name: "FortiOS 7.2" IPADDRESS: $(IPADDRESS_72) + OLDAUTH: false FortiOS74: name: "FortiOS 7.4" IPADDRESS: $(IPADDRESS_74) + OLDAUTH: false FortiOS76: name: "FortiOS 7.6" IPADDRESS: $(IPADDRESS_76) + OLDAUTH: false steps: - task: PowerShell@2 displayName: "Test PowerShell Core $(name)" @@ -147,6 +154,7 @@ stages: LOGIN: $(LOGIN) IPADDRESS: $(IPADDRESS) PASSWORD: $(PASSWORD) + OLDAUTH: $(OLDAUTH) - task: PublishTestResults@2 inputs: testResultsFormat: "NUnit" From 44e407928b6e82903779d2c05e099efdde603ce0 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Mon, 3 Nov 2025 20:35:21 +0100 Subject: [PATCH 10/12] Connection: Throw when code status HTTP 401 --- PowerFGT/Public/Connection.ps1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/PowerFGT/Public/Connection.ps1 b/PowerFGT/Public/Connection.ps1 index e2e287fa..7b16fa21 100644 --- a/PowerFGT/Public/Connection.ps1 +++ b/PowerFGT/Public/Connection.ps1 @@ -211,8 +211,14 @@ function Connect-FGT { $irmResponse = Invoke-RestMethod $uri -Method POST -Body ($postParams | ConvertTo-Json) -SessionVariable FGT @invokeParams } catch { - Show-FGTException $_ - throw "Unable to connect to FortiGate" + if ($_.Exception.Response.StatusCode.Value__ -eq "401" ) { + throw "Not supported API Authentication method on this FortiGate (try use -oldauth parameter)" + } + else { + Show-FGTException $_ + throw "Unable to connect to FortiGate" + } + } Write-verbose $irmResponse From 7aacbe3f543d44999fadb9c1335cf5578666d2cd Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Mon, 3 Nov 2025 20:38:24 +0100 Subject: [PATCH 11/12] Connection(Tests): use -oldauth tests when use before 6.4.0 --- Tests/integration/Connection.Tests.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/integration/Connection.Tests.ps1 b/Tests/integration/Connection.Tests.ps1 index 536a7f76..a19acf28 100644 --- a/Tests/integration/Connection.Tests.ps1 +++ b/Tests/integration/Connection.Tests.ps1 @@ -79,6 +79,10 @@ Describe "Connect to a fortigate (using HTTPS)" { $DefaultFGTConnection.version | Should -Not -BeNullOrEmpty $DefaultFGTConnection.serial | Should -Not -BeNullOrEmpty } + + It "Connect to a FortiGate (using HTTPS) to old (before FortiOS 6.4 without -oldauth)" -Skip:($httpOnly -and $fgt_version -ge "6.4.0" ) { + { Connect-FGT $ipaddress -Username $login -password $mysecpassword -SkipCertificateCheck -port $port } | Should -throw "Log in failure. Most likely an incorrect username/password combo" + } } Describe "Connect to a FortiGate (with post-login-banner enable)" { From 9c033192b6ee36d90e4d0c68a229544f41758471 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Tue, 4 Nov 2025 07:52:35 +0100 Subject: [PATCH 12/12] Connection(Tests): fix oldauth tests --- Tests/integration/Connection.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/integration/Connection.Tests.ps1 b/Tests/integration/Connection.Tests.ps1 index a19acf28..43cb301d 100644 --- a/Tests/integration/Connection.Tests.ps1 +++ b/Tests/integration/Connection.Tests.ps1 @@ -80,8 +80,8 @@ Describe "Connect to a fortigate (using HTTPS)" { $DefaultFGTConnection.serial | Should -Not -BeNullOrEmpty } - It "Connect to a FortiGate (using HTTPS) to old (before FortiOS 6.4 without -oldauth)" -Skip:($httpOnly -and $fgt_version -ge "6.4.0" ) { - { Connect-FGT $ipaddress -Username $login -password $mysecpassword -SkipCertificateCheck -port $port } | Should -throw "Log in failure. Most likely an incorrect username/password combo" + It "Connect to a FortiGate (using HTTPS) to old (before FortiOS 6.4 without -oldauth)" -Skip:($httpOnly -or $fgt_version -ge "6.4.0" ) { + { Connect-FGT $ipaddress -Username $login -password $mysecpassword -SkipCertificateCheck -port $port } | Should -throw "Not supported API Authentication method on this FortiGate (try use -oldauth parameter)" } }