From 34718ba8934ca03132b19d052c0781f72b7b74f5 Mon Sep 17 00:00:00 2001 From: Harrison Affel Date: Wed, 2 Oct 2024 10:09:21 -0400 Subject: [PATCH] update install script --- go.mod | 9 +-- go.sum | 22 +++---- install.ps1 | 96 +++++++++++++++++++++++++++++- tests/integration/install_test.ps1 | 81 +++++++++++++++++++++++++ tests/integration/network_test.ps1 | 4 +- tests/integration/process_test.ps1 | 3 +- tests/integration/utils.psm1 | 88 +++++++++++++++++++++++++-- 7 files changed, 280 insertions(+), 23 deletions(-) create mode 100644 tests/integration/install_test.ps1 diff --git a/go.mod b/go.mod index 46aada99..a271f058 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/mattn/go-colorable v0.1.13 github.com/pkg/errors v0.9.1 github.com/rancher/remotedialer v0.4.1 - github.com/rancher/system-agent v0.3.9 + github.com/rancher/system-agent v0.3.10-rc.1 github.com/sirupsen/logrus v1.9.3 github.com/urfave/cli/v2 v2.27.4 golang.org/x/sync v0.8.0 @@ -100,7 +100,7 @@ require ( github.com/google/cel-go v0.17.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/go-containerregistry v0.20.1 // indirect + github.com/google/go-containerregistry v0.20.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect @@ -126,7 +126,8 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29 // indirect - github.com/rancher/wharfie v0.6.6 // indirect + github.com/rancher/permissions v0.0.0-20240924180251-69b0dcb34065 // indirect + github.com/rancher/wharfie v0.6.7 // indirect github.com/rancher/wrangler v1.1.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/cobra v1.7.0 // indirect @@ -171,7 +172,7 @@ require ( k8s.io/cloud-provider v0.0.0 // indirect k8s.io/component-base v0.29.7 // indirect k8s.io/controller-manager v0.29.7 // indirect - k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kms v0.29.7 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/kubelet v0.24.2 // indirect diff --git a/go.sum b/go.sum index d704a1ec..d6b825cc 100644 --- a/go.sum +++ b/go.sum @@ -166,8 +166,8 @@ github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvR github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0= -github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= +github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -288,16 +288,18 @@ github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPH github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rancher/client-go v1.29.3-rancher1 h1:4nZ6BEtFLxBSomVgJFSCoOAmfo6hr8PYMwnsZk1Ubu8= github.com/rancher/client-go v1.29.3-rancher1/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= -github.com/rancher/dynamiclistener v0.3.5 h1:5TaIHvkDGmZKvc96Huur16zfTKOiLhDtK4S+WV0JA6A= -github.com/rancher/dynamiclistener v0.3.5/go.mod h1:dW/YF6/m2+uEyJ5VtEcd9THxda599HP6N9dSXk81+k0= +github.com/rancher/dynamiclistener v0.3.6 h1:iAFWeiFNra6tYlt4k+jINrK3hOxZ8mjW2S/9nA6sxKs= +github.com/rancher/dynamiclistener v0.3.6/go.mod h1:VqBaJNi+bZmre0+gi+2Jb6jbn7ovHzRueW+M7QhVKsk= github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29 h1:+kige/h8/LnzWgPjB5NUIHz/pWiW/lFpqcTUkN5uulY= github.com/rancher/lasso v0.0.0-20230830164424-d684fdeb6f29/go.mod h1:kgk9kJVMj9FIrrXU0iyM6u/9Je4bEjPImqswkTVaKsQ= +github.com/rancher/permissions v0.0.0-20240924180251-69b0dcb34065 h1:nJPrW/DdnSYQnKryQYlFXMs6nh12Q7MCW8Zb6l9Cb1A= +github.com/rancher/permissions v0.0.0-20240924180251-69b0dcb34065/go.mod h1:PDAb+l6/i6cbSokQ2CuNCgGOT/BHQY2WgZATwPXEyU4= github.com/rancher/remotedialer v0.4.1 h1:jwOf2kPRjBBpSFofv1OuZHWaYHeC9Eb6/XgDvbkoTgc= github.com/rancher/remotedialer v0.4.1/go.mod h1:Ys004RpJuTLSm+k4aYUCoFiOOad37ubYev3TkOFg/5w= -github.com/rancher/system-agent v0.3.9 h1:6t5EqF3n9yKePBhdSePCT1ASm8F4Gu+mjbnNrVMtzao= -github.com/rancher/system-agent v0.3.9/go.mod h1:hG3HockCxJkeUbcdxsYz3qHYaXv6O4fLiehlB28+szQ= -github.com/rancher/wharfie v0.6.6 h1:ESxPxBDiq9RXd8G9fC71qc7+AbetThVtxPC9K8VVZ2Y= -github.com/rancher/wharfie v0.6.6/go.mod h1:sfCy07HF8EE1MDKhpDc/cLptLTiTC0y/wisD44gr8uc= +github.com/rancher/system-agent v0.3.10-rc.1 h1:WjPVxWnHCiEBERgPur5faIDUyIkhb1fBLUEBJjuqkQw= +github.com/rancher/system-agent v0.3.10-rc.1/go.mod h1:pX+68YRd0Z/7PFgKxTWTq8WweviGTYAo7kCz2RsUK3g= +github.com/rancher/wharfie v0.6.7 h1:BhbBVJSLoDQMkZb+zVTLEKckUbq4sc3ZmEYqGakggSY= +github.com/rancher/wharfie v0.6.7/go.mod h1:ew49A9PzRsTngdzXIkgakfhMq3mHMA650HS1OVQpaNA= github.com/rancher/wrangler v1.1.1 h1:wmqUwqc2M7ADfXnBCJTFkTB5ZREWpD78rnZMzmxwMvM= github.com/rancher/wrangler v1.1.1/go.mod h1:ioVbKupzcBOdzsl55MvEDN0R1wdGggj8iNCYGFI5JvM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -576,8 +578,8 @@ k8s.io/component-base v0.29.7 h1:zXLJvZjvvDWdYmZCwZYk95E1Fd2oRXUz71mQukkRk5I= k8s.io/component-base v0.29.7/go.mod h1:ddLTpIrjazaRI1EG83M41GNcYEAdskuQmx4JOOSXCOg= k8s.io/controller-manager v0.29.7 h1:8FC9kQAm+BUTrAKyCS2uOaTXBytV3eEOIREfrFxaCjo= k8s.io/controller-manager v0.29.7/go.mod h1:lAua8GONLnkPAHPSzU0POmvHLhsKeHbjHnVtEQPfUno= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kms v0.29.7 h1:4ELQdx7T4EPKbN/QMj6SeZizrEKapza5YF8e5XtZPv0= k8s.io/kms v0.29.7/go.mod h1:vWVImKkJd+1BQY4tBwdfSwjQBiLrnbNtHADcDEDQFtk= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= diff --git a/install.ps1 b/install.ps1 index 516084d3..e60fb390 100644 --- a/install.ps1 +++ b/install.ps1 @@ -22,14 +22,15 @@ - CATTLE_AGENT_BINARY_URL (default: latest GitHub release) - CATTLE_PRESERVE_WORKDIR (default: false) - CATTLE_REMOTE_ENABLED (default: true) + - CATTLE_LOCAL_ENABLED (default: false) - CATTLE_ID (default: autogenerate) - CATTLE_AGENT_BINARY_LOCAL (default: false) - CATTLE_AGENT_BINARY_LOCAL_LOCATION (default: ) - CSI_PROXY_URL (default: ) - CSI_PROXY_VERSION (default: ) - CSI_PROXY_KUBELET_PATH (default: ) -.EXAMPLE - +.EXAMPLE + #> #Make sure this params matches the CmdletBinding below param ( @@ -213,6 +214,12 @@ function Invoke-WinsInstaller { $env:CATTLE_REMOTE_ENABLED = $env:CATTLE_REMOTE_ENABLED.ToLower() } + if (-Not $env:CATTLE_LOCAL_ENABLED) { + $env:CATTLE_LOCAL_ENABLED = "false" + } else { + $env:CATTLE_LOCAL_ENABLED = $env:CATTLE_LOCAL_ENABLED.ToLower() + } + if (-Not $env:CATTLE_PRESERVE_WORKDIR) { $env:CATTLE_PRESERVE_WORKDIR = "false" } @@ -277,6 +284,14 @@ function Invoke-WinsInstaller { } } + if (($env:CATTLE_REMOTE_ENABLED -eq "true") -and ($env:CATTLE_LOCAL_ENABLED -eq "true")){ + Write-LogFatal "Both CATTLE_LOCAL_ENABLED and CATTLE_REMOTE_ENABLED were enabled, exiting as only one can be enabled" + } + + if (($env:CATTLE_REMOTE_ENABLED -eq "false") -and ($env:CATTLE_LOCAL_ENABLED -eq "false")){ + Write-LogFatal "Neither CATTLE_LOCAL_ENABLED nor CATTLE_REMOTE_ENABLED were enabled, exiting as one must be enabled" + } + if (-Not $env:CATTLE_AGENT_CONFIG_DIR) { $env:CATTLE_AGENT_CONFIG_DIR = "C:/etc/rancher/wins" Write-LogInfo "Using default agent configuration directory $( $env:CATTLE_AGENT_CONFIG_DIR )" @@ -463,6 +478,7 @@ function Invoke-WinsInstaller { } } } + Set-RestrictedPermissions -Path $env:CATTLE_AGENT_VAR_DIR/rancher2_connection_info.json } } @@ -487,6 +503,7 @@ systemagent: workDirectory: $($env:CATTLE_AGENT_VAR_DIR)/work appliedPlanDirectory: $($env:CATTLE_AGENT_VAR_DIR)/applied remoteEnabled: $($env:CATTLE_REMOTE_ENABLED) + localEnabled: $($env:CATTLE_LOCAL_ENABLED) preserveWorkDirectory: $($env:CATTLE_PRESERVE_WORKDIR) "@ Add-Content -Path $env:CATTLE_AGENT_CONFIG_DIR/config -Value $agentConfig @@ -501,6 +518,7 @@ systemagent: "@ Add-Content -Path $env:CATTLE_AGENT_CONFIG_DIR/config -Value $tlsConfig } + Set-RestrictedPermissions -Path $env:CATTLE_AGENT_CONFIG_DIR/config } function Set-CsiProxyConfig() { @@ -614,10 +632,84 @@ csi-proxy: } } + function Set-RestrictedPermissions { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [string] + $Path, + [Parameter(Mandatory=$false)] + [Switch] + $Directory + ) + + $Owner = "BUILTIN\Administrators" + $Group = "NT AUTHORITY\SYSTEM" + + $acl = Get-Acl $Path + + # cleanup existing rules by removing both explicit and inherited rules. + foreach ($rule in $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])) { + $acl.RemoveAccessRule($rule) | Out-Null + } + + $acl.SetAccessRuleProtection($true, $false) + $acl.SetOwner((New-Object System.Security.Principal.NTAccount($Owner))) + $acl.SetGroup((New-Object System.Security.Principal.NTAccount($Group))) + + Set-FileSystemAccessRule -Directory $Directory -acl $acl + + Set-Acl -Path $Path -AclObject $acl + } + + function Set-FileSystemAccessRule() { + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [Boolean] + $Directory, + [Parameter(Mandatory=$false)] + [System.Security.AccessControl.ObjectSecurity] + $acl + ) + $users = @( + $acl.Owner, + $acl.Group + ) + # Note that the function signature for files and directories + # intentionally differ. + $FullPath = Resolve-Path $Path + if ($Directory -eq $true) { + Write-LogInfo "Setting restricted ACL on $FullPath directory" + foreach ($user in $users) { + $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( + $user, + [System.Security.AccessControl.FileSystemRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]'ObjectInherit,ContainerInherit', + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) + $acl.AddAccessRule($rule) + } + } else { + Write-LogInfo "Setting restricted ACL on $FullPath" + foreach ($user in $users) { + $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( + $user, + [System.Security.AccessControl.FileSystemRights]::FullControl, + [System.Security.AccessControl.AccessControlType]::Allow + ) + $acl.AddAccessRule($rule) + } + } + } + function Invoke-WinsAgentInstall() { $serviceName = "rancher-wins" Get-Args Set-Environment + Set-RestrictedPermissions -Path $env:CATTLE_AGENT_CONFIG_DIR -Directory + Set-RestrictedPermissions -Path $env:CATTLE_AGENT_VAR_DIR -Directory Set-Path Test-CaCheckSum diff --git a/tests/integration/install_test.ps1 b/tests/integration/install_test.ps1 new file mode 100644 index 00000000..74e68703 --- /dev/null +++ b/tests/integration/install_test.ps1 @@ -0,0 +1,81 @@ +$ErrorActionPreference = "Stop" + +Import-Module -Name @( + "$PSScriptRoot\utils.psm1" +) -WarningAction Ignore + +# clean interferences +try { + Get-Process -Name "rancher-wins-*" -ErrorAction Ignore | Stop-Process -Force -ErrorAction Ignore + Get-NetFirewallRule -PolicyStore ActiveStore -Name "rancher-wins-*" -ErrorAction Ignore | ForEach-Object { Remove-NetFirewallRule -Name $_.Name -PolicyStore ActiveStore -ErrorAction Ignore } | Out-Null + Get-Process -Name "wins" -ErrorAction Ignore | Stop-Process -Force -ErrorAction Ignore +} +catch { + Log-Warn $_.Exception.Message +} + +Describe "install" { + BeforeEach { + # note: we cannot test system agent install today since we need a mocked API server + Log-Info "Running install script" + # note: Simply running the install script does not do anything. During normal provisioning, + # Rancher will mutate the install script to both add environment variables, and to call + # the primary function 'Invoke-WinsInstaller'. As this is an integration test, we need to manually + # update the install script ourselves. + Add-Content -Path ./install.ps1 -Value '$env:CATTLE_REMOTE_ENABLED = "false"' + Add-Content -Path ./install.ps1 -Value '$env:CATTLE_LOCAL_ENABLED = "true"' + Add-Content -Path ./install.ps1 -Value Invoke-WinsInstaller + + .\install.ps1 + } + + AfterEach { + Log-Info "Running uninstall script" + try { + # note: since this script may not be run by an administrator, it's possible that it might fail + # on trying to delete certain files with ACLs attached to them. + # If you are running this locally, make sure you run with admin privileges. + # On CI, since we don't run as an admin today, this prevents automatic failure when the right ACLs are set. + .\uninstall.ps1 + } catch { + Log-Warn "You need to manually run uninstall.ps1, encountered error: $($_.Exception.Message)" + } + } + + It "creates files and directories with scoped down permissions" { + # While these get set in install.ps1, pester removes them as + # install.ps1 is called in the BeforeEach block + $env:CATTLE_AGENT_VAR_DIR = "c:/var/lib/rancher/agent" + $env:CATTLE_AGENT_CONFIG_DIR = "c:/etc/rancher/wins" + + $restrictedPaths = @( + $env:CATTLE_AGENT_VAR_DIR, + $env:CATTLE_AGENT_CONFIG_DIR, + "$env:CATTLE_AGENT_CONFIG_DIR/config" + + # TODO: to test the creation of rancher2_connection_info.json, we need to mock the Rancher server. + # Once this capability is added to tests, uncomment this and remove $env:CATTLE_REMOTE_ENABLED = "false" above. + # "$env:CATTLE_AGENT_VAR_DIR/rancher2_connection_info.json" + ) + foreach ($path in $restrictedPaths) { + Log-Info "Checking $path" + + Test-Path -Path $path | Should -Be $true + + Test-Permissions -Path $path -ExpectedOwner "BUILTIN\Administrators" -ExpectedGroup "NT AUTHORITY\SYSTEM" -ExpectedPermissions @( + [PSCustomObject]@{ + AccessMask = "FullControl" + Type = 0 + Identity = "NT AUTHORITY\SYSTEM" + }, + [PSCustomObject]@{ + AccessMask = "FullControl" + Type = 0 + Identity = "BUILTIN\Administrators" + } + ) + + Log-Info "Confirmed expected ACLs on $path" + } + } +} \ No newline at end of file diff --git a/tests/integration/network_test.ps1 b/tests/integration/network_test.ps1 index 05c24366..341ac266 100644 --- a/tests/integration/network_test.ps1 +++ b/tests/integration/network_test.ps1 @@ -74,7 +74,9 @@ Describe "network" { It "get default adapter" { # wins.exe cli network get # docker run --rm -v //./pipe/rancher_wins://./pipe/rancher_wins -v c:/etc/rancher/wins:c:/etc/rancher/wins wins-cli network get - $ret = Execute-Binary -FilePath "docker.exe" -ArgumentList @("run", "--rm", "-v", "//./pipe/rancher_wins://./pipe/rancher_wins", "-v", "c:/etc/rancher/wins:c:/etc/rancher/wins", "wins-cli", "network", "get") -PassThru + New-Directory "c:/etc/rancher/pipe" + + $ret = Execute-Binary -FilePath "docker.exe" -ArgumentList @("run", "--rm", "-v", "//./pipe/rancher_wins://./pipe/rancher_wins", "-v", "c:/etc/rancher/pipe:c:/etc/rancher/pipe", "wins-cli", "network", "get") -PassThru if (-not $ret.Ok) { Log-Error $ret.Output $false | Should -Be $true diff --git a/tests/integration/process_test.ps1 b/tests/integration/process_test.ps1 index 13572711..7be16146 100644 --- a/tests/integration/process_test.ps1 +++ b/tests/integration/process_test.ps1 @@ -87,6 +87,7 @@ Describe "process" { ) } } + New-Directory "c:\etc\rancher\wins" $config | ConvertTo-Json -Compress -Depth 32 | Out-File -NoNewline -Encoding utf8 -Force -FilePath "c:\etc\rancher\wins\config" $configJson = Get-Content -Raw -Path "c:\etc\rancher\wins\config" Log-Info $configJson @@ -97,7 +98,7 @@ Describe "process" { # wins.exe cli prc run --path xxx --exposes xxx # docker run --name prc-run --rm -v //./pipe/rancher_wins://./pipe/rancher_wins -v c:/etc/rancher/wins:c:/etc/rancher/wins -v c:/etc/nginx:c:/host/etc/nginx wins-nginx - Execute-Binary -FilePath "docker.exe" -ArgumentList @("run", "--name", "prc-run", "--rm", "-v", "//./pipe/rancher_wins://./pipe/rancher_wins", "-v", "c:/etc/rancher/wins:c:/etc/rancher/wins", "-v", "c:/etc/nginx:c:/host/etc/nginx", "wins-nginx") -Backgroud + Execute-Binary -FilePath "docker.exe" -ArgumentList @("run", "--name", "prc-run", "--rm", "-v", "//./pipe/rancher_wins://./pipe/rancher_wins", "-v", "c:/etc/rancher/pipe:c:/etc/rancher/pipe", "-v", "c:/etc/nginx:c:/host/etc/nginx", "wins-nginx") -Backgroud { Wait-Ready -Timeout 3 -Path "c:\etc\nginx\rancher-wins-nginx.exe" -Throw } | Should -Throw diff --git a/tests/integration/utils.psm1 b/tests/integration/utils.psm1 index f37b9c71..c7f6d4f0 100644 --- a/tests/integration/utils.psm1 +++ b/tests/integration/utils.psm1 @@ -1,20 +1,24 @@ function Log-Info { - Write-Host -NoNewline -ForegroundColor Blue "INFO " + $ts = (Get-Date).ToString("hh:mm:ss.fff") + Write-Host -NoNewline -ForegroundColor Blue "[INFO $ts] " Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) } function Log-Warn { - Write-Host -NoNewline -ForegroundColor DarkYellow "WARN " + $ts = (Get-Date).ToString("hh:mm:ss.fff") + Write-Host -NoNewline -ForegroundColor DarkYellow "[WARN $ts] " Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) } function Log-Error { - Write-Host -NoNewline -ForegroundColor DarkRed "ERRO " + $ts = (Get-Date).ToString("hh:mm:ss.fff") + Write-Host -NoNewline -ForegroundColor DarkRed "[ERRO $ts] " Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) } function Log-Fatal { - Write-Host -NoNewline -ForegroundColor DarkRed "FATA " + $ts = (Get-Date).ToString("hh:mm:ss.fff") + Write-Host -NoNewline -ForegroundColor DarkRed "[FATA $ts] " Write-Host -ForegroundColor Gray ("{0,-44}" -f ($args -join " ")) exit 255 @@ -178,6 +182,78 @@ function New-Directory { } } +function Get-Permissions { + param ( + [Parameter(Mandatory=$true)] + [string] + $Path + ) + + $exists = Test-Path $Path + if (-not $exists) { + throw "Cannot get permissions on path $Path if a file or directory does not exist" + } + + $acl = Get-Acl $Path + + $owner = $acl.Owner + $group = $acl.Group + $permissions = @() + foreach ($rule in $acl.Access) { + $permissions += [PSCustomObject]@{ + AccessMask = $rule.FileSystemRights.ToString() + Type = $rule.AccessControlType + Identity = $rule.IdentityReference.Value + } + } + + return $owner, $group, $permissions +} + +function Test-Permissions { + param ( + [Parameter(Mandatory=$true)] + [string] + $Path, + + [Parameter(Mandatory=$true)] + [string] + $ExpectedOwner, + + [Parameter(Mandatory=$true)] + [string] + $ExpectedGroup, + + [Parameter(Mandatory=$true)] + [System.Object[]] + $ExpectedPermissions + ) + + $owner, $group, $permissions = Get-Permissions -Path $Path + + $errors = @() + + if ($owner -ne $ExpectedOwner) { + $errors += "expected owner $ExpectedOwner, found $owner" + } + + if ($group -ne $ExpectedGroup) { + $errors += "expected group $ExpectedGroup, found $group" + } + + $expected = $ExpectedPermissions | ConvertTo-Json + $found = $permissions | ConvertTo-Json + + if ($expected -ne $found) { + $errors += "expected permissions $expected, found $found" + } + + # Check + if ($errors.Count -gt 0) { + $errors_joined = $errors -join "`n- " + throw "Permissions don't match expectations:`n- $errors_joined" + } +} Export-ModuleMember -Function Log-Info Export-ModuleMember -Function Log-Warn @@ -187,4 +263,6 @@ Export-ModuleMember -Function Execute-Binary Export-ModuleMember -Function Judge Export-ModuleMember -Function Wait-Ready Export-ModuleMember -Function New-Directory -Export-ModuleMember -Function Set-WinsConfig \ No newline at end of file +Export-ModuleMember -Function Set-WinsConfig +Export-ModuleMember -Function Get-Permissions +Export-ModuleMember -Function Test-Permissions