Public/DGateway.ps1
. "$PSScriptRoot/../Private/CertificateHelper.ps1" . "$PSScriptRoot/../Private/PlatformHelper.ps1" . "$PSScriptRoot/../Private/DockerHelper.ps1" . "$PSScriptRoot/../Private/TokenHelper.ps1" $script:DGatewayConfigFileName = 'gateway.json' $script:DGatewayCertificateFileName = 'server.crt' $script:DGatewayPrivateKeyFileName = 'server.key' $script:DGatewayProvisionerPublicKeyFileName = 'provisioner.pem' $script:DGatewayProvisionerPrivateKeyFileName = 'provisioner.key' $script:DGatewayDelegationPublicKeyFileName = 'delegation.pem' $script:DGatewayDelegationPrivateKeyFileName = 'delegation.key' function Get-DGatewayVersion { param( [Parameter(Mandatory = $true, Position = 0)] [ValidateSet('PSModule', 'Installed')] [string] $Type ) if ($Type -eq 'PSModule') { $ManifestPath = "$PSScriptRoot/../DevolutionsGateway.psd1" $Manifest = Import-PowerShellDataFile -Path $ManifestPath $DGatewayVersion = $Manifest.ModuleVersion } elseif ($Type -eq 'Installed') { if ($IsWindows) { $UninstallReg = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' ` | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -Match 'Devolutions Gateway' } if ($UninstallReg) { $DGatewayVersion = '20' + $UninstallReg.DisplayVersion } } elseif ($IsMacOS) { throw 'not supported' } elseif ($IsLinux) { $PackageName = 'devolutions-gateway' $DpkgStatus = $(dpkg -s $PackageName 2>$null) $DpkgMatches = $($DpkgStatus | Select-String -AllMatches -Pattern 'version: (\S+)').Matches if ($DpkgMatches) { $VersionQuad = $DpkgMatches.Groups[1].Value $VersionTriple = $VersionQuad -Replace '^(\d+)\.(\d+)\.(\d+)\.(\d+)$', "`$1.`$2.`$3" $DGatewayVersion = $VersionTriple } } } $DGatewayVersion } function Get-DGatewayImage { param( [string] $Platform ) $DGatewayVersion = Get-DGatewayVersion 'PSModule' $image = if ($Platform -ne 'windows') { "devolutions/devolutions-gateway:${DGatewayVersion}-buster" } else { "devolutions/devolutions-gateway:${DGatewayVersion}-servercore-ltsc2019" } return $image } class DGatewayListener { [string] $InternalUrl [string] $ExternalUrl DGatewayListener() { } DGatewayListener([string] $InternalUrl, [string] $ExternalUrl) { $this.InternalUrl = $InternalUrl $this.ExternalUrl = $ExternalUrl } } function New-DGatewayListener() { [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [string] $ListenerUrl, [Parameter(Mandatory = $true, Position = 1)] [string] $ExternalUrl ) return [DGatewayListener]::new($ListenerUrl, $ExternalUrl) } class DGatewaySubProvisionerKey { [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Id [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Value [ValidateNotNullOrEmpty()] [ValidateSet('Spki','Rsa')] [string] $Format [ValidateNotNullOrEmpty()] [ValidateSet('Multibase','Base64', 'Base64Pad', 'Base64Url', 'Base64UrlPad')] [string] $Encoding DGatewaySubProvisionerKey( [string] $Id, [string] $Value, [string] $Format = 'Spki', [string] $Encoding = 'Multibase' ) { $this.Id = $Id $this.Value = $Value $this.Format = $Format $this.Encoding = $Encoding } DGatewaySubProvisionerKey([PSCustomObject] $object) { $this.Id = $object.Id $this.Value = $object.Value $this.Format = $object.Format $this.Encoding = $object.Encoding } DGatewaySubProvisionerKey([Hashtable] $table) { $this.Id = $table.Id $this.Value = $table.Value $this.Format = $table.Format $this.Encoding = $table.Encoding } } class DGatewaySubscriber { [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [System.Uri] $Url [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Token DGatewaySubscriber( [System.Uri] $Url, [string] $Token ) { $this.Url = $Url $this.Token = $Token } DGatewaySubscriber([PSCustomObject] $object) { $this.Url = $object.Url $this.Token = $object.Token } DGatewaySubscriber([Hashtable] $table) { $this.Url = $table.Url $this.Token = $table.Token } } class DGatewayConfig { [System.Nullable[Guid]] $Id [string] $Hostname [string] $FarmName [string[]] $FarmMembers [string] $TlsCertificateFile [string] $TlsPrivateKeyFile [string] $ProvisionerPublicKeyFile [string] $ProvisionerPrivateKeyFile [string] $DelegationPublicKeyFile [string] $DelegationPrivateKeyFile [DGatewaySubProvisionerKey] $SubProvisionerPublicKey [DGatewayListener[]] $Listeners [DGatewaySubscriber] $Subscriber [string] $DockerPlatform [string] $DockerIsolation [string] $DockerRestartPolicy [string] $DockerImage [string] $DockerContainerName [string] $LogDirective } function Save-DGatewayConfig { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Mandatory = $true)] [DGatewayConfig] $Config ) $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 [System.IO.File]::WriteAllLines($ConfigFile, $ConfigData, $(New-Object System.Text.UTF8Encoding $False)) } function Set-DGatewayConfig { [CmdletBinding()] param( [string] $ConfigPath, [string] $Force, [Guid] $Id, [string] $Hostname, [string] $FarmName, [string[]] $FarmMembers, [DGatewayListener[]] $Listeners, [DGatewaySubscriber] $Subscriber, [Obsolete("You should use TlsCertificateFile")] [string] $CertificateFile, [Obsolete("You should use TlsPrivateKeyFile")] [string] $PrivateKeyFile, [string] $TlsCertificateFile, [string] $TlsPrivateKeyFile, [string] $ProvisionerPublicKeyFile, [string] $ProvisionerPrivateKeyFile, [string] $DelegationPublicKeyFile, [string] $DelegationPrivateKeyFile, [DGatewaySubProvisionerKey] $SubProvisionerPublicKey, [ValidateSet('linux', 'windows')] [string] $DockerPlatform, [ValidateSet('process', 'hyperv')] [string] $DockerIsolation, [ValidateSet('no', 'on-failure', 'always', 'unless-stopped')] [string] $DockerRestartPolicy, [string] $DockerImage, [string] $DockerContainerName ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath if (-Not (Test-Path -Path $ConfigPath -PathType 'Container')) { New-Item -Path $ConfigPath -ItemType 'Directory' } $ConfigFile = Join-Path $ConfigPath $DGatewayConfigFileName if (-Not (Test-Path -Path $ConfigFile -PathType 'Leaf')) { $config = [DGatewayConfig]::new() } else { $config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties } if ($PSBoundParameters.ContainsKey('CertificateFile')) { $PSBoundParameters.TlsCertificateFile = $CertificateFile } if ($PSBoundParameters.ContainsKey('PrivateKeyFile')) { $PSBoundParameters.TlsPrivateKeyFile = $PrivateKeyFile } $properties = [DGatewayConfig].GetProperties() | ForEach-Object { $_.Name } foreach ($param in $PSBoundParameters.GetEnumerator()) { if ($properties -Contains $param.Key) { $config.($param.Key) = $param.Value } } Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function Get-DGatewayConfig { [CmdletBinding()] [OutputType('DGatewayConfig')] param( [string] $ConfigPath, [switch] $NullProperties, [switch] $Expand ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $ConfigFile = Join-Path $ConfigPath $DGatewayConfigFileName $config = [DGatewayConfig]::new() if (-Not (Test-Path -Path $ConfigFile -PathType 'Leaf')) { if ($NullProperties) { return $config } } $ConfigData = Get-Content -Path $ConfigFile -Encoding UTF8 $json = $ConfigData | ConvertFrom-Json [DGatewayConfig].GetProperties() | ForEach-Object { $Name = $_.Name if ($json.PSObject.Properties[$Name]) { $Property = $json.PSObject.Properties[$Name] $Value = $Property.Value $config.$Name = $Value } } if ($json.CertificateFile -Ne $null) { $config.TlsCertificateFile = $json.CertificateFile } if ($json.PrivateKeyFile -Ne $null) { $config.TlsPrivateKeyFile = $json.PrivateKeyFile } if ($Expand) { Expand-DGatewayConfig $config } if (-Not $NullProperties) { $Properties = $Config.PSObject.Properties.Name $NonNullProperties = $Properties.Where( { -Not [string]::IsNullOrEmpty($Config.$_) }) $Config = $Config | Select-Object $NonNullProperties } return $config } function Expand-DGatewayConfig { param( [DGatewayConfig] $Config ) if (-Not $config.DockerPlatform) { if ($IsWindows) { $config.DockerPlatform = 'windows' } else { $config.DockerPlatform = 'linux' } } if (-Not $config.DockerRestartPolicy) { $config.DockerRestartPolicy = 'always' } if (-Not $config.DockerImage) { $config.DockerImage = Get-DGatewayImage -Platform $config.DockerPlatform } if (-Not $config.DockerContainerName) { $config.DockerContainerName = 'devolutions-gateway' } } function Find-DGatewayConfig { [CmdletBinding()] param( [string] $ConfigPath ) if (-Not $ConfigPath) { $CurrentPath = Get-Location $ConfigFile = Join-Path $CurrentPath $DGatewayConfigFileName if (Test-Path -Path $ConfigFile -PathType 'Leaf') { $ConfigPath = $CurrentPath } } if (-Not $ConfigPath) { $ConfigPath = Get-DGatewayPath } if ($Env:DGATEWAY_CONFIG_PATH) { $ConfigPath = $Env:DGATEWAY_CONFIG_PATH } return $ConfigPath } function Enter-DGatewayConfig { [CmdletBinding()] param( [string] $ConfigPath, [switch] $ChangeDirectory ) if ($ConfigPath) { $ConfigPath = Resolve-Path $ConfigPath $Env:DGATEWAY_CONFIG_PATH = $ConfigPath } $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath if ($ChangeDirectory) { Set-Location $ConfigPath } } function Exit-DGatewayConfig { Remove-Item Env:DGATEWAY_CONFIG_PATH } function Get-DGatewayPath() { [CmdletBinding()] param( [Parameter(Position = 0)] [ValidateSet('ConfigPath')] [string] $PathType = 'ConfigPath' ) $DisplayName = 'Gateway' $CompanyName = 'Devolutions' if ($IsWindows) { $ConfigPath = $Env:ProgramData + "\${CompanyName}\${DisplayName}" } elseif ($IsMacOS) { $ConfigPath = "/Library/Application Support/${CompanyName} ${DisplayName}" } elseif ($IsLinux) { $ConfigPath = '/etc/devolutions-gateway' } switch ($PathType) { 'ConfigPath' { $ConfigPath } default { throw("Invalid path type: $PathType") } } } function Get-DGatewayFarmName { [CmdletBinding()] param( [string] $ConfigPath ) $(Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties).FarmName } function Set-DGatewayFarmName { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Mandatory = $true, Position = 0)] [string] $FarmName ) $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties $Config.FarmName = $FarmName Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function Get-DGatewayFarmMembers { [CmdletBinding()] param( [string] $ConfigPath ) $(Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties).FarmMembers } function Set-DGatewayFarmMembers { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Mandatory = $true, Position = 0)] [AllowEmptyCollection()] [string[]] $FarmMembers ) $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties $Config.FarmMembers = $FarmMembers Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function Get-DGatewayHostname { [CmdletBinding()] param( [string] $ConfigPath ) $(Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties).Hostname } function Set-DGatewayHostname { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Mandatory = $true, Position = 0)] [string] $Hostname ) $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties $Config.Hostname = $Hostname Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function Get-DGatewayListeners { [CmdletBinding()] [OutputType('DGatewayListener[]')] param( [string] $ConfigPath ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties $Config.Listeners } function Set-DGatewayListeners { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Mandatory = $true, Position = 0)] [AllowEmptyCollection()] [DGatewayListener[]] $Listeners ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties $Config.Listeners = $Listeners Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function Import-DGatewayCertificate { [CmdletBinding()] param( [string] $ConfigPath, [string] $CertificateFile, [string] $PrivateKeyFile, [string] $Password ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties $result = Get-PemCertificate -CertificateFile:$CertificateFile ` -PrivateKeyFile:$PrivateKeyFile -Password:$Password $CertificateData = $result.Certificate $PrivateKeyData = $result.PrivateKey New-Item -Path $ConfigPath -ItemType 'Directory' -Force | Out-Null $CertificateFile = Join-Path $ConfigPath $DGatewayCertificateFileName $PrivateKeyFile = Join-Path $ConfigPath $DGatewayPrivateKeyFileName Set-Content -Path $CertificateFile -Value $CertificateData -Force Set-Content -Path $PrivateKeyFile -Value $PrivateKeyData -Force $Config.TlsCertificateFile = $DGatewayCertificateFileName $Config.TlsPrivateKeyFile = $DGatewayPrivateKeyFileName Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function New-DGatewayProvisionerKeyPair { [CmdletBinding()] param( [string] $ConfigPath, [int] $KeySize = 2048, [switch] $Force ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties if (-Not (Test-Path -Path $ConfigPath)) { New-Item -Path $ConfigPath -ItemType 'Directory' -Force | Out-Null } $PublicKeyFile = Join-Path $ConfigPath $DGatewayProvisionerPublicKeyFileName $PrivateKeyFile = Join-Path $ConfigPath $DGatewayProvisionerPrivateKeyFileName if ((Test-Path -Path $PublicKeyFile) -Or (Test-Path -Path $PrivateKeyFile)) { if (-Not $Force) { throw "$PublicKeyFile or $PrivateKeyFile already exists, use -Force to overwrite" } Remove-Item $PublicKeyFile -Force | Out-Null Remove-Item $PrivateKeyFile -Force | Out-Null } $KeyPair = New-RsaKeyPair -KeySize:$KeySize $PublicKeyData = $KeyPair.PublicKey $Config.ProvisionerPublicKeyFile = $DGatewayProvisionerPublicKeyFileName Set-Content -Path $PublicKeyFile -Value $PublicKeyData -Force $PrivateKeyData = $KeyPair.PrivateKey $Config.ProvisionerPrivateKeyFile = $DGatewayProvisionerPrivateKeyFileName Set-Content -Path $PrivateKeyFile -Value $PrivateKeyData -Force Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function Import-DGatewayProvisionerKey { [CmdletBinding()] param( [string] $ConfigPath, [string] $PublicKeyFile, [string] $PrivateKeyFile ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties if ($PublicKeyFile) { $PublicKeyData = Get-Content -Path $PublicKeyFile -Encoding UTF8 $OutputFile = Join-Path $ConfigPath $DGatewayProvisionerPublicKeyFileName $Config.ProvisionerPublicKeyFile = $DGatewayProvisionerPublicKeyFileName New-Item -Path $ConfigPath -ItemType 'Directory' -Force | Out-Null Set-Content -Path $OutputFile -Value $PublicKeyData -Force } if ($PrivateKeyFile) { $PrivateKeyData = Get-Content -Path $PrivateKeyFile -Encoding UTF8 $OutputFile = Join-Path $ConfigPath $DGatewayProvisionerPrivateKeyFileName $Config.ProvisionerPrivateKeyFile = $DGatewayProvisionerPrivateKeyFileName New-Item -Path $ConfigPath -ItemType 'Directory' -Force | Out-Null Set-Content -Path $OutputFile -Value $PrivateKeyData -Force } Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function New-DGatewayDelegationKeyPair { [CmdletBinding()] param( [string] $ConfigPath, [int] $KeySize = 2048, [switch] $Force ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties if (-Not (Test-Path -Path $ConfigPath)) { New-Item -Path $ConfigPath -ItemType 'Directory' -Force | Out-Null } $PublicKeyFile = Join-Path $ConfigPath $DGatewayDelegationPublicKeyFileName $PrivateKeyFile = Join-Path $ConfigPath $DGatewayDelegationPrivateKeyFileName if ((Test-Path -Path $PublicKeyFile) -Or (Test-Path -Path $PrivateKeyFile)) { if (-Not $Force) { throw "$PublicKeyFile or $PrivateKeyFile already exists, use -Force to overwrite" } Remove-Item $PublicKeyFile -Force | Out-Null Remove-Item $PrivateKeyFile -Force | Out-Null } $KeyPair = New-RsaKeyPair -KeySize:$KeySize $PublicKeyData = $KeyPair.PublicKey $Config.DelegationPublicKeyFile = $DGatewayDelegationPublicKeyFileName Set-Content -Path $PublicKeyFile -Value $PublicKeyData -Force $PrivateKeyData = $KeyPair.PrivateKey $Config.DelegationPrivateKeyFile = $DGatewayDelegationPrivateKeyFileName Set-Content -Path $PrivateKeyFile -Value $PrivateKeyData -Force Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function Import-DGatewayDelegationKey { [CmdletBinding()] param( [string] $ConfigPath, [string] $PublicKeyFile, [string] $PrivateKeyFile ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties if ($PublicKeyFile) { $PublicKeyData = Get-Content -Path $PublicKeyFile -Encoding UTF8 $OutputFile = Join-Path $ConfigPath $DGatewayDelegationPublicKeyFileName $Config.DelegationPublicKeyFile = $DGatewayDelegationPublicKeyFileName New-Item -Path $ConfigPath -ItemType 'Directory' -Force | Out-Null Set-Content -Path $OutputFile -Value $PublicKeyData -Force } if ($PrivateKeyFile) { $PrivateKeyData = Get-Content -Path $PrivateKeyFile -Encoding UTF8 $OutputFile = Join-Path $ConfigPath $DGatewayDelegationPrivateKeyFileName $Config.DelegationPrivateKeyFile = $DGatewayDelegationPrivateKeyFileName New-Item -Path $ConfigPath -ItemType 'Directory' -Force | Out-Null Set-Content -Path $OutputFile -Value $PrivateKeyData -Force } Save-DGatewayConfig -ConfigPath:$ConfigPath -Config:$Config } function New-DGatewayToken { [CmdletBinding()] param( [string] $ConfigPath, [ValidateSet('ASSOCIATION', 'SCOPE', 'BRIDGE', 'JMUX')] [Parameter(Mandatory = $true)] [string] $Type, #type # public common claims [DateTime] $ExpirationTime, # exp [DateTime] $NotBefore, # nbf [DateTime] $IssuedAt, # iat # private association claims [string] $AssociationId, # jet_aid [ValidateSet('unknown', 'wayk', 'rdp', 'ard', 'vnc', 'ssh', 'ssh-pwsh', 'sftp', 'scp', 'winrm-http-pwsh', 'winrm-https-pwsh', 'http', 'https')] [string] $ApplicationProtocol, # jet_ap [ValidateSet('fwd', 'rdv')] [string] $ConnectionMode, # jet_cm [string] $DestinationHost, # dst_hst #private scope claims [string] $Scope, # scope #private bridge claims [string] $Target, # target # signature parameters [string] $PrivateKeyFile ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties if (-Not $PrivateKeyFile) { if (-Not $Config.ProvisionerPrivateKeyFile) { throw "Config file is missing ``ProvisionerPrivateKeyFile``. Alternatively, use -PrivateKeyFile argument." } if ([System.IO.Path]::IsPathRooted($Config.ProvisionerPrivateKeyFile)) { $PrivateKeyFile = $Config.ProvisionerPrivateKeyFile } else { $PrivateKeyFile = Join-Path $ConfigPath $Config.ProvisionerPrivateKeyFile } } if (-Not (Test-Path -Path $PrivateKeyFile -PathType 'Leaf')) { throw "$PrivateKeyFile cannot be found." } $PrivateKey = ConvertTo-RsaPrivateKey $(Get-Content $PrivateKeyFile -Raw) $CurrentTime = Get-Date if (-Not $NotBefore) { $NotBefore = $CurrentTime } if (-Not $IssuedAt) { $IssuedAt = $CurrentTime } if (-Not $ExpirationTime) { $ExpirationTime = $CurrentTime.AddMinutes(2) } $iat = [System.DateTimeOffset]::new($IssuedAt).ToUnixTimeSeconds() $nbf = [System.DateTimeOffset]::new($NotBefore).ToUnixTimeSeconds() $exp = [System.DateTimeOffset]::new($ExpirationTime).ToUnixTimeSeconds() $jti = (New-Guid).ToString() $Header = [PSCustomObject]@{ alg = 'RS256' typ = 'JWT' cty = $Type } $Payload = [PSCustomObject]@{ iat = $iat nbf = $nbf exp = $exp jti = $jti } if ($Type -eq 'ASSOCIATION') { if (-Not $ApplicationProtocol) { if ($ConnectionMode -eq 'fwd') { $ApplicationProtocol = 'rdp' } else { $ApplicationProtocol = 'wayk' } } $Payload | Add-Member -MemberType NoteProperty -Name 'jet_ap' -Value $ApplicationProtocol if (-Not $ConnectionMode) { if ($DestinationHost) { $ConnectionMode = 'fwd' } else { $ConnectionMode = 'rdv' } } $Payload | Add-Member -MemberType NoteProperty -Name 'jet_cm' -Value $ConnectionMode if (-Not $AssociationId) { $AssociationId = New-Guid } $Payload | Add-Member -MemberType NoteProperty -Name 'jet_aid' -Value $AssociationId if ($DestinationHost) { $Payload | Add-Member -MemberType NoteProperty -Name 'dst_hst' -Value $DestinationHost } } if ($Type -eq 'JMUX') { if (-Not $DestinationHost) { throw "-DestinationHost is required" } if ($ApplicationProtocol) { $Payload | Add-Member -MemberType NoteProperty -Name 'jet_ap' -Value $ApplicationProtocol } if (-Not $AssociationId) { $AssociationId = New-Guid } $Payload | Add-Member -MemberType NoteProperty -Name 'jet_aid' -Value $AssociationId $Payload | Add-Member -MemberType NoteProperty -Name 'dst_hst' -Value $DestinationHost } if (($Type -eq 'SCOPE') -and ($Scope)) { $Payload | Add-Member -MemberType NoteProperty -Name 'scope' -Value $Scope } if (($Type -eq 'BRIDGE') -and ($Target)) { $Payload | Add-Member -MemberType NoteProperty -Name 'target' -Value $Target } New-JwtRs256 -Header $Header -Payload $Payload -PrivateKey $PrivateKey } function Get-DGatewayService { param( [string] $ConfigPath, [DGatewayConfig] $Config ) if ($config.DockerPlatform -eq 'linux') { $ContainerConfigPath = '/etc/devolutions-gateway' } else { $ContainerConfigPath = 'C:\ProgramData\Devolutions\Gateway' } $Service = [DockerService]::new() $Service.ContainerName = $config.DockerContainerName $Service.Image = $config.DockerImage $Service.Platform = $config.DockerPlatform $Service.Isolation = $config.DockerIsolation $Service.RestartPolicy = $config.DockerRestartPolicy $Service.TargetPorts = @() foreach ($Listener in $config.Listeners) { $InternalUrl = $Listener.InternalUrl -Replace '://\*', '://localhost' $url = [System.Uri]::new($InternalUrl) $Service.TargetPorts += @($url.Port) } $Service.TargetPorts = $Service.TargetPorts | Select-Object -Unique $Service.PublishAll = $true $Service.Environment = [ordered]@{ 'DGATEWAY_CONFIG_PATH' = $ContainerConfigPath; 'RUST_BACKTRACE' = '1'; 'RUST_LOG' = 'info'; } $Service.Volumes = @("${ConfigPath}:${ContainerConfigPath}") $Service.External = $false return $Service } function Get-DGatewayPackage { [CmdletBinding()] param( [string] $RequiredVersion, [ValidateSet('Windows', 'Linux')] [string] $Platform ) $Version = Get-DGatewayVersion 'PSModule' if ($RequiredVersion) { $Version = $RequiredVersion } if (-Not $Platform) { if ($IsWindows) { $Platform = 'Windows' } else { $Platform = 'Linux' } } $GitHubDownloadUrl = 'https://github.com/Devolutions/devolutions-gateway/releases/download/' if ($Platform -eq 'Windows') { $Architecture = 'x86_64' $PackageFileName = "DevolutionsGateway-${Architecture}-${Version}.msi" } elseif ($Platform -eq 'Linux') { $Architecture = 'amd64' $PackageFileName = "devolutions-gateway_${Version}.0_${Architecture}.deb" } $DownloadUrl = "${GitHubDownloadUrl}v${Version}/$PackageFileName" [PSCustomObject]@{ Url = $DownloadUrl; Version = $Version; } } function Install-DGatewayPackage { [CmdletBinding()] param( [string] $RequiredVersion, [switch] $Quiet, [switch] $Force ) $Version = Get-DGatewayVersion 'PSModule' if ($RequiredVersion) { $Version = $RequiredVersion } $InstalledVersion = Get-DGatewayVersion 'Installed' if (($InstalledVersion -eq $Version) -and (-Not $Force)) { Write-Host "Devolutions Gateway is already installed ($Version)" return } $TempPath = Join-Path $([System.IO.Path]::GetTempPath()) "dgateway-${Version}" New-Item -ItemType Directory -Path $TempPath -ErrorAction SilentlyContinue | Out-Null $Package = Get-DGatewayPackage -RequiredVersion $Version $DownloadUrl = $Package.Url $DownloadFile = Split-Path -Path $DownloadUrl -Leaf $DownloadFilePath = Join-Path $TempPath $DownloadFile Write-Host "Downloading $DownloadUrl" $WebClient = [System.Net.WebClient]::new() $WebClient.DownloadFile($DownloadUrl, $DownloadFilePath) $WebClient.Dispose() $DownloadFilePath = Resolve-Path $DownloadFilePath if ($IsWindows) { $Display = '/passive' if ($Quiet) { $Display = '/quiet' } $InstallLogFile = Join-Path $TempPath 'DGateway_Install.log' $MsiArgs = @( '/i', "`"$DownloadFilePath`"", $Display, '/norestart', '/log', "`"$InstallLogFile`"" ) Start-Process 'msiexec.exe' -ArgumentList $MsiArgs -Wait -NoNewWindow Remove-Item -Path $InstallLogFile -Force -ErrorAction SilentlyContinue } elseif ($IsMacOS) { throw 'unsupported platform' } elseif ($IsLinux) { $DpkgArgs = @( '-i', $DownloadFilePath ) if ((id -u) -eq 0) { Start-Process 'dpkg' -ArgumentList $DpkgArgs -Wait } else { $DpkgArgs = @('dpkg') + $DpkgArgs Start-Process 'sudo' -ArgumentList $DpkgArgs -Wait } } Remove-Item -Path $TempPath -Force -Recurse } function Uninstall-DGatewayPackage { [CmdletBinding()] param( [switch] $Quiet ) if ($IsWindows) { $UninstallReg = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' ` | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object { $_ -Match 'Devolutions Gateway' } if ($UninstallReg) { $UninstallString = $($UninstallReg.UninstallString ` -Replace 'msiexec.exe', '' -Replace '/I', '' -Replace '/X', '').Trim() $Display = '/passive' if ($Quiet) { $Display = '/quiet' } $MsiArgs = @( '/X', $UninstallString, $Display ) Start-Process 'msiexec.exe' -ArgumentList $MsiArgs -Wait } } elseif ($IsMacOS) { throw 'unsupported platform' } elseif ($IsLinux) { if (Get-DGatewayVersion 'Installed') { $AptArgs = @( '-y', 'remove', 'devolutions-gateway', '--purge' ) if ((id -u) -eq 0) { Start-Process 'apt-get' -ArgumentList $AptArgs -Wait } else { $AptArgs = @('apt-get') + $AptArgs Start-Process 'sudo' -ArgumentList $AptArgs -Wait } } } } function Update-DGatewayImage { [CmdletBinding()] param( [string] $ConfigPath ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties Expand-DGatewayConfig -Config $config $Service = Get-DGatewayService -ConfigPath:$ConfigPath -Config:$config Request-ContainerImage -Name $Service.Image } function Get-DGatewayLaunchType { [CmdletBinding()] param( [Parameter(Position = 0)] [ValidateSet('Detect', 'Container', 'Service')] [string] $LaunchType = 'Detect' ) if ($LaunchType -eq 'Detect') { if (Get-DGatewayVersion 'Installed') { $LaunchType = 'Service' } elseif ($(Get-Command 'docker' -ErrorAction SilentlyContinue)) { $LaunchType = 'Container' } else { $LaunchType = $null } } $LaunchType } function Start-DGateway { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Position = 0)] [ValidateSet('Detect', 'Container', 'Service')] [string] $LaunchType = 'Detect' ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties Expand-DGatewayConfig -Config:$config $LaunchType = Get-DGatewayLaunchType $LaunchType if (-Not $LaunchType) { throw 'No suitable Devolutions Gateway launch type detected' } if ($LaunchType -eq 'Container') { $Service = Get-DGatewayService -ConfigPath:$ConfigPath -Config:$config # pull docker images only if they are not cached locally if (-Not (Get-ContainerImageId -Name $Service.Image)) { Request-ContainerImage -Name $Service.Image } Start-DockerService -Service $Service -Remove } elseif ($LaunchType -eq 'Service') { if ($IsWindows) { Start-Service 'DevolutionsGateway' } else { throw 'not implemented' } } } function Stop-DGateway { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Position = 0)] [ValidateSet('Detect', 'Container', 'Service')] [string] $LaunchType = 'Detect', [switch] $Remove ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath $config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties Expand-DGatewayConfig -Config $config $LaunchType = Get-DGatewayLaunchType $LaunchType if (-Not $LaunchType) { throw 'No suitable Devolutions Gateway launch type detected' } if ($LaunchType -eq 'Container') { $Service = Get-DGatewayService -ConfigPath:$ConfigPath -Config:$config Write-Host "Stopping $($Service.ContainerName)" Stop-Container -Name $Service.ContainerName -Quiet if ($Remove) { Remove-Container -Name $Service.ContainerName } } elseif ($LaunchType -eq 'Service') { if ($IsWindows) { Stop-Service 'DevolutionsGateway' } else { throw 'not implemented' } } } function Restart-DGateway { [CmdletBinding()] param( [string] $ConfigPath, [Parameter(Position = 0)] [ValidateSet('Detect', 'Container', 'Service')] [string] $LaunchType = 'Detect' ) $ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath Stop-DGateway -ConfigPath:$ConfigPath -LaunchType:$LaunchType Start-DGateway -ConfigPath:$ConfigPath -LaunchType:$LaunchType } |