Public/Set-LinuxFilePermission.ps1
|
#!/usr/bin/env pwsh Set-StrictMode -Version Latest if ($IsLinux) { function Set-LinuxFilePermission { <# .SYNOPSIS Tightens the owner and mode of system files, cron directories, and home directories to a rule table. .DESCRIPTION Reads the rules in Config/LinuxFilePermission.psd1, or the file given with -LiteralPath, and applies each one to every path it matches. The rules cover the account databases, login banners, cron directories, and home directories, using only values that hold on every distribution. Mode is a floor: bits the rule does not allow are removed, and none are added, so a repair never grants access a path did not already have. Owner and Group, when a rule has them, are set with chown before the mode, because chown clears the setuid and setgid bits. Paths that do not exist are skipped. Symbolic links are skipped with a warning, because chown and chmod would change the file the link points at. Requires root. Supports -WhatIf and -Confirm. Linux only. .PARAMETER LiteralPath A .psd1 rule file to apply instead of Config/LinuxFilePermission.psd1. It must hold a Rule list of hashtables with Path, Mode, and optionally Owner and Group. .PARAMETER PassThru Returns each path that was changed. .INPUTS None. You can't pipe objects to this function. .OUTPUTS System.IO.FileSystemInfo. With -PassThru, one object per changed path. Otherwise nothing. .EXAMPLE Set-LinuxFilePermission -WhatIf Lists each path that would change, with its owner and mode before and after. .EXAMPLE Invoke-Sudo { Import-Module ./PSSecurity.psd1; Set-LinuxFilePermission -PassThru } Applies the rules as root and lists the paths that changed. .EXAMPLE Set-LinuxFilePermission -LiteralPath ./MyPermission.psd1 Applies a custom rule file. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.IO.FileSystemInfo])] param( [Parameter(HelpMessage = 'Enter a .psd1 rule file to apply instead of Config/LinuxFilePermission.psd1.')] [ValidateNotNullOrEmpty()] [string]$LiteralPath, [Parameter(HelpMessage = 'Return each path that was changed.')] [switch]$PassThru ) if (-not $PSBoundParameters.ContainsKey('LiteralPath')) { $LiteralPath = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Config' -AdditionalChildPath 'LinuxFilePermission.psd1' } $rules = (Import-PowerShellDataFile -LiteralPath $LiteralPath).Rule foreach ($rule in $rules) { $floor = [System.IO.UnixFileMode][System.Convert]::ToInt32($rule.Mode, 8) $hasOwner = $rule.ContainsKey('Owner') foreach ($item in Get-Item -Path $rule.Path -Force -ErrorAction Ignore) { if ($item.LinkType) { Write-Warning -Message "Skipped $($item.FullName): it is a $($item.LinkType) to $($item.Target)." continue } $newMode = $item.UnixFileMode -band $floor $needsOwner = $hasOwner -and ($item.User -ne $rule.Owner -or $item.Group -ne $rule.Group) $needsMode = $newMode -ne $item.UnixFileMode if (-not ($needsOwner -or $needsMode)) { Write-Verbose -Message "$($item.FullName) already complies." continue } $owner = if ($needsOwner) { "$($rule.Owner):$($rule.Group)" } else { "$($item.User):$($item.Group)" } $oldOctal = [System.Convert]::ToString([int]$item.UnixFileMode, 8) $newOctal = [System.Convert]::ToString([int]$newMode, 8) $change = "Set $owner $newOctal, was $($item.User):$($item.Group) $oldOctal" if ($PSCmdlet.ShouldProcess($item.FullName, $change)) { if ($needsOwner) { (chown -- $owner $item.FullName) if ($LASTEXITCODE -ne 0) { throw "Set-LinuxFilePermission failed to change the owner of $($item.FullName) to $owner." } } [System.IO.File]::SetUnixFileMode($item.FullName, $newMode) if ($PassThru) { Get-Item -LiteralPath $item.FullName -Force } } } } } } |