Skip to content

Commit

Permalink
feat(pwsh): add powershell user management with argon2 password hashi…
Browse files Browse the repository at this point in the history
…ng (#658)
  • Loading branch information
awakecoding authored Jan 20, 2024
1 parent 754951b commit 7157ad6
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 1 deletion.
1 change: 1 addition & 0 deletions powershell/DevolutionsGateway/DevolutionsGateway.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
'New-DGatewayDelegationKeyPair', 'Import-DGatewayDelegationKey',
'New-DGatewayToken',
'New-DGatewayWebAppConfig',
'Set-DGatewayUser', 'Remove-DGatewayUser', 'Get-DGatewayUser',
'Start-DGateway', 'Stop-DGateway', 'Restart-DGateway',
'Get-DGatewayVersion', 'Get-DGatewayPackage',
'Install-DGatewayPackage', 'Uninstall-DGatewayPackage')
Expand Down
138 changes: 138 additions & 0 deletions powershell/DevolutionsGateway/Public/DGateway.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ $script:DGatewayProvisionerPublicKeyFileName = 'provisioner.pem'
$script:DGatewayProvisionerPrivateKeyFileName = 'provisioner.key'
$script:DGatewayDelegationPublicKeyFileName = 'delegation.pem'
$script:DGatewayDelegationPrivateKeyFileName = 'delegation.key'
$script:DGatewayCustomUsersFileName = 'users.txt'

function Get-DGatewayVersion {
param(
Expand Down Expand Up @@ -258,6 +259,7 @@ class DGatewayWebAppConfig {
[string] $Authentication
[System.Nullable[System.UInt32]] $AppTokenMaximumLifetime
[System.Nullable[System.UInt32]] $LoginLimitRate
[string] $UsersFile

DGatewayWebAppConfig() { }
}
Expand Down Expand Up @@ -1056,6 +1058,142 @@ function New-DGatewayToken {
New-JwtRs256 -Header $Header -Payload $Payload -PrivateKey $PrivateKey
}

function ConvertTo-DGatewayHash
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[string] $Password
)

$parameters = [Devolutions.Picky.Argon2Params]::New.Invoke(@())
$algorithm = [Devolutions.Picky.Argon2Algorithm]::Argon2id
$argon2 = [Devolutions.Picky.Argon2]::New.Invoke(@($algorithm, $parameters))
$argon2.HashPassword($Password)
}

function Get-DGatewayUsersFilePath
{
[CmdletBinding()]
param(
[string] $ConfigPath
)

$ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath
$Config = Get-DGatewayConfig -ConfigPath:$ConfigPath -NullProperties

if ($Config.WebApp.UsersFile) {
$fileName = $Config.WebApp.UsersFile
} else {
$fileName = $script:DGatewayCustomUsersFileName
}

$filePath = Join-Path -Path $ConfigPath -ChildPath $fileName
return $filePath
}

function Set-DGatewayUser {
[CmdletBinding()]
param(
[string] $ConfigPath,
[Parameter(Mandatory=$true, Position=0)]
[string] $Username,
[Parameter(Mandatory=$true, Position=1)]
[string] $Password
)

$ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath

$filePath = Get-DGatewayUsersFilePath -ConfigPath $ConfigPath
$hash = ConvertTo-DGatewayHash -Password $Password

$fileContent = @()
if (Test-Path $filePath) {
try {
$fileContent = [System.IO.File]::ReadLines($filePath)
}
catch {
Write-Host "Error reading file: $_"
return
}
}

$entry = "$Username`:$hash"
$updated = $false

$fileContentList = New-Object System.Collections.Generic.List[System.String]
foreach ($line in $fileContent) {
$fileContentList.Add($line)
}

for ($i = 0; $i -lt $fileContentList.Count; $i++) {
if ((-Not [string]::IsNullOrEmpty($fileContentList[$i])) -And
$fileContentList[$i].StartsWith("${Username}:")) {
$fileContentList[$i] = $entry
$updated = $true
break
}
}

if (-Not $updated) {
$fileContentList.Add($entry)
}

[System.IO.File]::WriteAllLines($filePath, $fileContentList)
}

function Remove-DGatewayUser {
[CmdletBinding()]
param(
[string] $ConfigPath,
[Parameter(Mandatory=$true, Position=0)]
[string] $Username
)

$ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath

$filePath = Get-DGatewayUsersFilePath -ConfigPath $ConfigPath
$fileContent = Get-Content $filePath

$newContent = $fileContent | Where-Object { $_ -notmatch "^${Username}:" }
Set-Content -Path $filePath -Value $newContent
}

function Get-DGatewayUser {
[CmdletBinding()]
param(
[string] $ConfigPath,
[string] $Username
)

$ConfigPath = Find-DGatewayConfig -ConfigPath:$ConfigPath

$filePath = Get-DGatewayUsersFilePath -ConfigPath $ConfigPath
$fileContent = Get-Content $filePath
$users = @()

foreach ($line in $fileContent) {
# Splitting at the first ':' character
$splitIndex = $line.IndexOf(':')
if ($splitIndex -lt 0) { continue }

$user = $line.Substring(0, $splitIndex)
$hash = $line.Substring($splitIndex + 1)

$users += New-Object PSObject -Property @{
User = $user
Hash = $hash
}
}

if ($Username) {
$user = $users | Where-Object { $_.User -eq $Username }
return $user
} else {
return $users
}
}

function Get-DGatewayPackage {
[CmdletBinding()]
param(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Devolutions.Picky" Version="2023.9.20" />
<PackageReference Include="Devolutions.Picky" Version="2024.1.20" />
<PackageReference Include="PowerShellStandard.Library" Version="5.1.0" />
<PackageReference Include="System.Collections" Version="4.3.0" />
<PackageReference Include="System.Diagnostics.Debug" Version="4.3.0" />
Expand Down
50 changes: 50 additions & 0 deletions powershell/pester/Users.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Import-Module "$PSScriptRoot/../DevolutionsGateway"

Describe 'Devolutions Gateway User Management' {
InModuleScope DevolutionsGateway {
BeforeAll {
$ConfigPath = Join-Path $TestDrive 'Gateway'
Set-DGatewayConfig -ConfigPath:$ConfigPath -Hostname 'gateway.local'
$filePath = Join-Path -Path $ConfigPath -ChildPath "users.txt"
Set-Content -Path $filePath -Value ""
}

Context 'User Management' {
It 'Can add a new user' {
# Set the user's password
Set-DGatewayUser -Username "admin" -Password 'password' -ConfigPath $ConfigPath

# Retrieve the user and test if a hash exists
$user = Get-DGatewayUser -Username "admin" -ConfigPath $ConfigPath
$user.Hash | Should -Not -BeNullOrEmpty
}

It 'Can edit an existing user' {
# Get the current hash of the user
$originalUser = Get-DGatewayUser -Username "admin" -ConfigPath $ConfigPath
$originalHash = $originalUser.Hash

# Update the user with a new hash
Set-DGatewayUser -Username "admin" -Password 'newpass' -ConfigPath $ConfigPath

# Retrieve the updated user and hash
$updatedUser = Get-DGatewayUser -Username "admin" -ConfigPath $ConfigPath
$updatedHash = $updatedUser.Hash

# The test should pass if the original hash and the updated hash are different
$updatedHash | Should -Not -Be $originalHash
}

It 'Can remove a user' {
Remove-DGatewayUser -Username "admin" -ConfigPath $ConfigPath
$user = Get-DGatewayUser -Username "admin" -ConfigPath $ConfigPath
$user | Should -Be $null
}

It 'Handles non-existing user correctly' {
$user = Get-DGatewayUser -Username "nonexistent" -ConfigPath $ConfigPath
$user | Should -Be $null
}
}
}
}

0 comments on commit 7157ad6

Please sign in to comment.