Skip to content

Commit

Permalink
feat(pwsh): verbosity profile, ngrok tunnel configuration (#577)
Browse files Browse the repository at this point in the history
  • Loading branch information
awakecoding authored Oct 21, 2023
1 parent c30de99 commit 51c4d9c
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 13 deletions.
1 change: 1 addition & 0 deletions powershell/DevolutionsGateway/DevolutionsGateway.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
FunctionsToExport = @(
'Find-DGatewayConfig', 'Enter-DGatewayConfig', 'Exit-DGatewayConfig',
'Set-DGatewayConfig', 'Get-DGatewayConfig',
'New-DGatewayNgrokConfig', 'New-DGatewayNgrokTunnel',
'Set-DGatewayHostname', 'Get-DGatewayHostname',
'New-DGatewayListener', 'Get-DGatewayListeners', 'Set-DGatewayListeners',
'Get-DGatewayPath', 'Get-DGatewayRecordingPath',
Expand Down
152 changes: 147 additions & 5 deletions powershell/DevolutionsGateway/Public/DGateway.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,125 @@ class DGatewaySubscriber {
}
}

class DGatewayNgrokTunnel {
[string] $Proto
[string] $Metadata
[string[]] $AllowCidrs
[string[]] $DenyCidrs

# HTTP tunnel
[string] $Domain
[System.Nullable[System.Single]] $CircuitBreaker
[System.Nullable[System.Boolean]] $Compression

# TCP tunnel
[string] $RemoteAddr

DGatewayNgrokTunnel() { }
}

class DGatewayNgrokConfig {
[string] $AuthToken
[System.Nullable[System.UInt32]] $HeartbeatInterval
[System.Nullable[System.UInt32]] $HeartbeatTolerance
[string] $Metadata
[string] $ServerAddr
[PSCustomObject] $Tunnels

DGatewayNgrokConfig() { }
}

function New-DGatewayNgrokTunnel() {
[CmdletBinding(DefaultParameterSetName = 'http')]
param(
[Parameter(Mandatory = $false, ParameterSetName = 'http',
HelpMessage = "HTTP tunnel")]
[switch] $Http,

[Parameter(Mandatory = $false, ParameterSetName = 'tcp',
HelpMessage = "TCP tunnel")]
[switch] $Tcp,

[Parameter(Mandatory = $false,
HelpMessage = "User-defined metadata that appears when listing tunnel sessions with ngrok")]
[string] $Metadata,

[ValidateScript({
$_ -match '^((\d{1,3}\.){3}\d{1,3}\/\d{1,2}|([\dA-Fa-f]{0,4}:){2,7}[\dA-Fa-f]{0,4}\/\d{1,3})$'
})]
[Parameter(Mandatory = $false,
HelpMessage = "Reject connections that do not match the given CIDRs")]
[string[]] $AllowCidrs,

[ValidateScript({
$_ -match '^((\d{1,3}\.){3}\d{1,3}\/\d{1,2}|([\dA-Fa-f]{0,4}:){2,7}[\dA-Fa-f]{0,4}\/\d{1,3})$'
})]
[Parameter(Mandatory = $false,
HelpMessage = "Reject connections that match the given CIDRs")]
[string[]] $DenyCidrs,

[ValidateScript({
$_ -match '^(\*\.)?([a-zA-Z0-9](-?[a-zA-Z0-9])*\.)*[a-zA-Z]{2,}$'
})]
[Parameter(Mandatory = $false, ParameterSetName = 'http',
HelpMessage = "Any valid domain or hostname previously registered with ngrok")]
[string] $Domain,

[ValidateRange(0.0, 1.0)]
[Parameter(Mandatory = $false, ParameterSetName = 'http',
HelpMessage = "Reject requests when 5XX responses exceed this ratio")]
[System.Single] $CircuitBreaker,

[Parameter(Mandatory = $false, ParameterSetName = 'http',
HelpMessage = "Use gzip compression on HTTP responses")]
[System.Boolean] $Compression,

[ValidateScript({
$_ -match '^([a-zA-Z0-9](-?[a-zA-Z0-9])*\.)*[a-zA-Z]{2,}:\d{1,5}$'
})]
[Parameter(Mandatory = $false, ParameterSetName = 'tcp',
HelpMessage = "The remote TCP address and port to bind. For example: remote_addr: 2.tcp.ngrok.io:21746")]
[string] $RemoteAddr
)

$tunnel = [DGatewayNgrokTunnel]::new()

if ($Tcp) {
$tunnel.Proto = "tcp"
} else {
$tunnel.Proto = "http"
}

$properties = [DGatewayNgrokTunnel].GetProperties() | ForEach-Object { $_.Name }
foreach ($param in $PSBoundParameters.GetEnumerator()) {
if ($properties -Contains $param.Key) {
$tunnel.($param.Key) = $param.Value
}
}

$tunnel
}

function New-DGatewayNgrokConfig() {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string] $AuthToken
)

$ngrok = [DGatewayNgrokConfig]::new()
$ngrok.AuthToken = $AuthToken
$ngrok
}

enum VerbosityProfile {
Default
Debug
Tls
All
Quiet
}

class DGatewayConfig {
[System.Nullable[Guid]] $Id
[string] $Hostname
Expand All @@ -159,7 +278,28 @@ class DGatewayConfig {
[DGatewayListener[]] $Listeners
[DGatewaySubscriber] $Subscriber

[DGatewayNgrokConfig] $Ngrok

[string] $LogDirective
[string] $VerbosityProfile
}

Function Remove-NullObjectProperties {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, Mandatory)]
[object[]] $InputObject
)
process {
foreach ($OldObj in $InputObject) {
$NonNullProperties = $OldObj.PSObject.Properties.Name.Where( { -Not [string]::IsNullOrEmpty($OldObj.$_) })
$NewObj = $OldObj | Select-Object $NonNullProperties
$NewObj.PSObject.Properties | Where-Object { $_.TypeNameOfValue.EndsWith('PSCustomObject') } | ForEach-Object {
$NewObj."$($_.Name)" = $NewObj."$($_.Name)" | Remove-NullObjectProperties
}
$NewObj
}
}
}

function Save-DGatewayConfig {
Expand All @@ -172,10 +312,8 @@ function Save-DGatewayConfig {

$ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath
$ConfigFile = Join-Path $ConfigPath $DGatewayConfigFileName

$Properties = $Config.PSObject.Properties.Name
$NonNullProperties = $Properties.Where( { -Not [string]::IsNullOrEmpty($Config.$_) })
$ConfigData = $Config | Select-Object $NonNullProperties | ConvertTo-Json
$ConfigClean = $Config | ConvertTo-Json -Depth 4 | ConvertFrom-Json # drop class type info
$ConfigData = $ConfigClean | Remove-NullObjectProperties | ConvertTo-Json -Depth 4

[System.IO.File]::WriteAllLines($ConfigFile, $ConfigData, $(New-Object System.Text.UTF8Encoding $False))
}
Expand Down Expand Up @@ -203,7 +341,11 @@ function Set-DGatewayConfig {
[string] $DelegationPublicKeyFile,
[string] $DelegationPrivateKeyFile,

[DGatewaySubProvisionerKey] $SubProvisionerPublicKey
[DGatewaySubProvisionerKey] $SubProvisionerPublicKey,

[DGatewayNgrokConfig] $Ngrok,

[VerbosityProfile] $VerbosityProfile
)

$ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath
Expand Down
54 changes: 54 additions & 0 deletions powershell/pester/Config.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Describe 'Devolutions Gateway config' {

Context 'Fresh environment' {
It 'Creates basic configuration' {
Remove-Item (Join-Path $ConfigPath 'gateway.json') -ErrorAction SilentlyContinue | Out-Null
Set-DGatewayConfig -ConfigPath:$ConfigPath -Hostname 'gateway.local'
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).Hostname | Should -Be 'gateway.local'
}
Expand Down Expand Up @@ -43,6 +44,59 @@ Describe 'Devolutions Gateway config' {
Set-DGatewayConfig -ConfigPath:$ConfigPath -RecordingPath $RecordingPath
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).RecordingPath | Should -Be $RecordingPath
}

It 'Sets log verbosity profile' {
Set-DGatewayConfig -ConfigPath:$ConfigPath -VerbosityProfile 'All'
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).VerbosityProfile | Should -Be 'All'
Set-DGatewayConfig -ConfigPath:$ConfigPath -VerbosityProfile 'Default'
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).VerbosityProfile | Should -Be 'Default'
Set-DGatewayConfig -ConfigPath:$ConfigPath -VerbosityProfile 'Tls'
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).VerbosityProfile | Should -Be 'Tls'
{ Set-DGatewayConfig -ConfigPath:$ConfigPath -VerbosityProfile 'yolo' } | Should -Throw
}

It 'Sets ngrok configuration' {
$AuthToken = '4nq9771bPxe8ctg7LKr_2ClH7Y15Zqe4bWLWF9p'
$Metadata = '{"serial": "00012xa-33rUtz9", "comment": "For customer alan@example.com"}'
$HeartbeatInterval = 15
$HeartbeatTolerance = 5
$ngrok = New-DGatewayNgrokConfig -AuthToken $AuthToken
$ngrok.Metadata = $Metadata
$ngrok.HeartbeatInterval = $HeartbeatInterval
$ngrok.HeartbeatTolerance = $HeartbeatTolerance
$httpTunnelParams = @{
Http = $true
Metadata = "c6481452-6f5d-11ee-b962-0242ac120002"
AllowCidrs = @("0.0.0.0/0")
Domain = "gateway.ngrok.io"
CircuitBreaker = 0.5
Compression = $true
}
$tcpTunnelParams = @{
Tcp = $true
AllowCidrs = @("0.0.0.0/0")
RemoteAddr = "7.tcp.ngrok.io:20560"
}
$httpTunnel = New-DGatewayNgrokTunnel @httpTunnelParams
$tcpTunnel = New-DGatewayNgrokTunnel @tcpTunnelParams
$ngrok.Tunnels = [PSCustomObject]@{
"http-endpoint" = $httpTunnel
"tcp-endpoint" = $tcpTunnel
}
Set-DGatewayConfig -ConfigPath:$ConfigPath -Ngrok $ngrok
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).Ngrok.AuthToken | Should -Be $AuthToken
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).Ngrok.Metadata | Should -Be $Metadata
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).Ngrok.HeartbeatInterval | Should -Be $HeartbeatInterval
$(Get-DGatewayConfig -ConfigPath:$ConfigPath).Ngrok.HeartbeatTolerance | Should -Be $HeartbeatTolerance
$Tunnels = $(Get-DGatewayConfig -ConfigPath:$ConfigPath).Ngrok.Tunnels
$Tunnels.'http-endpoint'.Proto | Should -Be 'http'
$Tunnels.'http-endpoint'.Domain | Should -Be $httpTunnel.Domain
$Tunnels.'http-endpoint'.Metadata | Should -Be $httpTunnel.Metadata
$Tunnels.'http-endpoint'.AllowCidrs | Should -Be $httpTunnel.AllowCidrs
$Tunnels.'tcp-endpoint'.Proto | Should -Be 'tcp'
$Tunnels.'tcp-endpoint'.RemoteAddr | Should -Be $tcpTunnel.RemoteAddr
$Tunnels.'tcp-endpoint'.AllowCidrs | Should -Be $tcpTunnel.AllowCidrs
}
}
}
}
13 changes: 7 additions & 6 deletions powershell/pester/ImportCertificate.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,32 @@ Describe 'Devolutions Gateway certificate import' {
BeforeAll {
$DummyPrivateKey = Join-Path $TestDrive 'dummy.key'
New-Item -Path $DummyPrivateKey -Value 'dummy'

$ConfigPath = Join-Path $TestDrive 'Gateway'
}

It 'Smoke' {
ForEach ($certFile in Get-ChildItem -Path ./ImportCertificate/WellOrdered) {
(Get-Item -Path ".\ImportCertificate\WellOrdered\*.crt") | ForEach-Object {
$CertFile = $_.FullName
$expected = Get-Content -Path $certFile

Import-DGatewayCertificate -ConfigPath:$ConfigPath -CertificateFile $certFile -PrivateKeyFile $DummyPrivateKey

$resultingFile = Join-Path $TestDrive 'Gateway' 'server.crt'
$resultingFile = Join-Path $ConfigPath 'server.crt'
$result = Get-Content -Path $resultingFile

$result | Should -Be $expected
}
}

It 'Sorting' {
ForEach ($unorderedCertFile in Get-ChildItem -Path ./ImportCertificate/Unordered) {
$wellOrderedCertFile = Join-Path './ImportCertificate/WellOrdered' $unorderedCertFile.Name
(Get-Item -Path ".\ImportCertificate\Unordered\*.crt") | ForEach-Object {
$unorderedCertFile = $_.FullName
$wellOrderedCertFile = $_.FullName -Replace 'Unordered', 'WellOrdered'
$expected = Get-Content -Path $wellOrderedCertFile

Import-DGatewayCertificate -ConfigPath:$ConfigPath -CertificateFile $unorderedCertFile -PrivateKeyFile $DummyPrivateKey

$resultingFile = Join-Path $TestDrive 'Gateway' 'server.crt'
$resultingFile = Join-Path $ConfigPath 'server.crt'
$result = Get-Content -Path $ResultingFile

$result | Should -Be $expected
Expand Down
3 changes: 1 addition & 2 deletions powershell/run-tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ Import-Module Pester
Push-Location -Path $(Join-Path $PSScriptRoot 'pester')

try {
Invoke-Pester .
Invoke-Pester . -Show All
} finally {
Pop-Location
}

0 comments on commit 51c4d9c

Please sign in to comment.