diff --git a/PowerFGT/Public/Connection.ps1 b/PowerFGT/Public/Connection.ps1 index efb3c62b..7b16fa21 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,86 @@ 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 { + 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 + #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 "ccsrf*" ) { + #before 7.6 it was ccsrftoken_port_xxxx, from 7.6.3+ it is ccsrf_token_port_xxxx + $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) { @@ -210,8 +292,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... @@ -350,6 +433,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)" } @@ -446,10 +530,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 } diff --git a/Tests/common.ps1 b/Tests/common.ps1 index 9f2cbd87..aad096c0 100644 --- a/Tests/common.ps1 +++ b/Tests/common.ps1 @@ -100,6 +100,13 @@ else { $invokeParams.add('SkipCertificateCheck', $SkipCertificateCheck) $invokeParams.add('httpOnly', $httpOnly) $invokeParams.add('port', $port) +if ($oldauth -eq "true") { + $script:oldauth = $true +} +else { + $script:oldauth = $false +} +$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" diff --git a/Tests/integration/Connection.Tests.ps1 b/Tests/integration/Connection.Tests.ps1 index 99f8b41c..43cb301d 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) { @@ -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 -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)" + } } Describe "Connect to a FortiGate (with post-login-banner enable)" { diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9fddd0dc..098ed22c 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" @@ -79,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)" @@ -118,6 +133,7 @@ stages: LOGIN: $(LOGIN) IPADDRESS: $(IPADDRESS) PASSWORD: $(PASSWORD) + OLDAUTH: $(OLDAUTH) - task: PowerShell@2 displayName: "Test PowerShell 5 $(name)" inputs: @@ -138,6 +154,7 @@ stages: LOGIN: $(LOGIN) IPADDRESS: $(IPADDRESS) PASSWORD: $(PASSWORD) + OLDAUTH: $(OLDAUTH) - task: PublishTestResults@2 inputs: testResultsFormat: "NUnit"