function getKernelVersion {
	$vsplit = $(uname -r).split('-')
	if ($vsplit[1] -match '\.') { # Fedora
		$vsplit[1] = $vsplit[1].split('.')[0]
	}
	return [version]($vsplit[0] + '.' + $vsplit[1])
}
function commandExists {
	param (
		$command
	)
    return [bool](Get-Command -Name $command -ErrorAction SilentlyContinue)
}
[AuditTest] @{
	Id = "DSBD-001"
	Task = "Ensure the system is booting in UEFI mode."
	Test = {
		if (Test-Path -Path /sys/firmware/efi) {
			$status = @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			$status = @{
				Message = "System is not booting using UEFI mode."
				Status = "False"
			}
		}
		return $status
	}
}
[AuditTest] @{
	Id = "DSBD-002"
	Task = "Ensure the system is using SecureBoot."
	Test = {
		if (Test-Path -Path /sys/firmware/efi) {
			if ($(mokutil --sb-state) -eq "SecureBoot enabled") {
				$status = @{
							Message = "Compliant"
							Status = "True"
				}
			} else {
				$status = @{
					Message = "System is not booting using UEFI mode."
					Status = "False"
				}
			}
		} else {
			$status = @{
				Message = "SecureBoot is only supported on UEFI."
				Status= "False"
			}
		}
		return $status
	}
}
[AuditTest] @{
	Id = "DSBD-003"
	Task = "Ensure the system has a TPM Chip."
	Test = {
		if (Test-Path -Path /dev/tpm0) { # /dev/tpmrm0 is _only_ for TPM 2.0
			$status = @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			$status = @{
				Message = "Could not detect a TPM chip"
				Status = "False"
			}
		}
		return $status
	}
}
[AuditTest] @{
	Id = "DSBD-004"
	Task = "Ensure the TPM Chip is implementing specification version 2.0 or higher."
	Test = {
		if ($(getKernelVersion) -ge [version]'5.6.0.0') { # For Ubuntu 20.04 e.g.
			$spec = [float](Get-Content -Path '/sys/class/tpm/tpm0/tpm_version_major')
		} else {
		$tpm2toolsMajorVersion = [int]($(tpm2_getcap -v) | Select-String -Pattern '^.+version=\"(\d)\..+$').Matches.Groups[1].Value
			if ($tpm2toolsMajorVersion -le 3) { # old versions up to 3.x had a different syntax (Debian 9)
				$text = $(tpm2_getcap -c properties-fixed)
				$match = [regex]::matches($text, '(?smi)TPM_PT_FAMILY_INDICATOR:   as UINT32: +0[xX][0-9a-fA-F]+   as string: +\"(\d\.\d)\"').Groups[1].Value
			} else { # new versions 4.x (RHEL 8)
				$text = $(tpm2_getcap properties-fixed)
				$match = [regex]::matches($text, '(?smi)TPM2_PT_FAMILY_INDICATOR:   raw: +0[xX][0-9a-fA-F]+   value: +\"(\d\.\d)\"').Groups[1].Value
			}
			$spec = [float]$match
		}

		if ($spec -ge 2.0) {
			return @{
				Message = "Compliant"
				Status = "True"
			}
		} elseif ($spec -gt 0) {
			return @{
				Message = "Specification version lower than 2.0 found."
				Status = "Warning"
			}
		} else {
			return @{
				Message = "No implemented specification version found."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-005"
	Task = "Report the count of local users on the system."
	Test = {
		# Linux native alternative: grep -c ^ /etc/passwd
		$countUsers = (Get-Content /etc/passwd).Count
		return  @{
					Message = "System has $countUsers local users"
					Status = "None"
		}
	}
}
[AuditTest] @{
	Id = "DSBD-006"
	Task = "Report the count of local interactive users on the system."
	Test = {
		$countUsers = (Get-Content /etc/passwd | Where-Object {-not ($_ -match "/usr/sbin/nologin" -or $_ -match "/bin/false" -or $_ -match "/bin/sync")}).Count
		$status = switch ($countUsers) {
			{($PSItem -ge 0) -and ($PSItem -le 2)}{ # 0, 1, 2
				@{
					Message = "Compliant"
					Status = "True"
				}
			}
			{($PSItem -gt 2) -and ($PSItem -le 5)}{ # 3, 4, 5
				@{
					Message = "System has 3-5 local users."
					Status = "Warning"
				}
			}
			{$PSItem -gt 5}{ # 6, ...
				@{
					Message = "System has 6 or more local users."
					Status = "False"
				}
			}
			Default {
				@{
					Message = "Cannot determine the count of local users"
					Status = "Error"
				}
			}
		}
		return $status
	}
}
[AuditTest] @{
	Id = "DSBD-007"
	Task = "Get the count of admin users on the system."
	Test = {
		$usersSudo = ($(getent group sudo) -split ":")[3]
		$usersRoot = ($(getent group root) -split ":")[3]
		$usersWheel = ($(getent group wheel) -split ":")[3]
		$usersAdmin = ($(getent group admin) -split ":")[3]
		$usersAdm = ($(getent group adm) -split ":")[3]
		$userIdZero = ($(getent passwd 0) -split ":")[0]
		$allUsersArr = @($usersSudo, $usersRoot, $usersWheel, $usersAdmin, $usersAdm, $userIdZero) | Where-Object {$_ -ne "" -and $_ -ne $null} | Sort-Object | Get-Unique
		$status = switch ($allUsersArr.Count) {
			{($PSItem -ge 0) -and ($PSItem -le 2)}{ # 0, 1, 2
				@{
					Message = "Compliant"
					Status = "True"
				}
			}
			{($PSItem -gt 2) -and ($PSItem -le 5)}{ # 3, 4, 5
				@{
					Message = "System has 3-5 admin users."
					Status = "Warning"
				}
			}
			{$PSItem -gt 5}{ # 6, ...
				@{
					Message = "System has 6 or more admin users."
					Status = "False"
				}
			}
			Default {
				@{
					Message = "Cannot determine the count of admin users"
					Status = "Error"
				}
			}
		}
		return $status
	}
}
[AuditTest] @{
	Id = "DSBD-008"
	Task = "Ensure the NX bit is set."
	Test = {
		$query = (-split (Get-Content /proc/cpuinfo | Where-Object {$_ -match '^flags.*$'} | Get-Unique)) -Contains 'nx'
		if ($query) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "The NX bit is not set."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-009"
	Task = "Ensure the ASLR is enabled."
	Test = {
		$query = [int](Get-Content /proc/sys/kernel/randomize_va_space)
		if ($query -ge 2) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} elseif ($query -eq 1) {
			return @{
				Message = "ASLR is partially enabled."
				Status = "Warning"
			}
		} else {
			return @{
				Message = "ASLR is not enabled."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-010"
	Task = "Ensure AppArmor or SELinux is enabled."
	Test = {
		if (commandExists 'aa-status') {
			$AppArmorStatus = ($(aa-status) -match '^apparmor module is loaded.*$').Count -gt 0
		}
		if (commandExists 'getenforce') {
			$SELinuxStatus = ($(getenforce) -match 'Enforcing$').Count -gt 0
		}

		if ($AppArmorStatus -or $SELinuxStatus) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "Neither AppArmor nor SELinux are enabled."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-011"
	Task = "Ensure CPU has no known vulnerabilities."
	Test = {
		$query = ((Get-Content /sys/devices/system/cpu/vulnerabilities/*) -match '^Vulnerable.*$').Count
		if ($query -eq 0) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "System has $query known CPU vulnerabilities."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-012"
	Task = "Ensure root login using SSH is not permitted."
	Test = {
		$rootLoginDisabled = [bool](Get-Content /etc/ssh/sshd_config | Select-String -Pattern '^PermitRootLogin no').Matches.Length
		if ($rootLoginDisabled) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "Login for root using SSH is permitted."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-013"
	Task = "Ensure a firewall is installed (ufw, iptables, nftables)."
	Test = {
		if (commandExists dpkg) {
			$ufwInstalled = [bool]($(dpkg -s ufw 2> /dev/zero) -match 'Status: install').Count
			$iptablesInstalled = [bool]($(dpkg -s iptables 2> /dev/zero) -match 'Status: install').Count
			$nftablesInstalled = [bool]($(dpkg -s nftables 2> /dev/zero) -match 'Status: install').Count
		}
		if (commandExists rpm) {
			$ufwInstalled = [bool]($(rpm -qa) -match '^ufw.+$').Count
			$iptablesInstalled = [bool]($(rpm -qa) -match '^iptables.+$').Count
			$nftablesInstalled = [bool]($(rpm -qa) -match '^nftables.+$').Count
		}
		if ($ufwInstalled -or $iptablesInstalled -or $nftablesInstalled) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "Login for root using SSH is permitted."
				Status = "False"
			}
		}
	}
}
function getFilePermissionsRegex {
	Param(
		[Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$filePath
	)
	$text = stat $filePath
	$match = [regex]::matches($text, '\S+:\s+\((\d+)\/\S+\)\s+Uid:\s+\(\s*(\d+)\/\s+(\S+)\)\s+Gid:\s+\(\s+(\d+)\/\s+(\S+)\)')
	return [ordered]@{
		permissionsOct = $match.Groups[1].Value
		ownerUserId = $match.Groups[2].Value
		ownerUserName = $match.Groups[3].Value
		ownerGroupId = $match.Groups[4].Value
		ownerGroupName = $match.Groups[5].Value
	}
}
function checkFilePermissions {
	Param(
		[Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$filePath,
		[Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$permissionsOct,
		[Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$ownerUserName,
		[Parameter(Mandatory=$true)][String][ValidateNotNullOrEmpty()]$ownerGroupName
	)
	# calculate mode
	$item = Get-Item $filePath
	$modeLowerBits = $item.UnixStat.Mode -band 4095 # 4095_(10) = 111111111111_(2) = 7777_(8) = FFF_(16)
	$mode = [Convert]::ToString($modeLowerBits, 8) # Conversion not necessary in future: https://github.com/PowerShell/PowerShell/issues/16757 , alternative: stat -c '%a' /etc/passwd
	# check for same or more restricted permissions
	foreach ($i in 0..($mode.Length - 1)) {
		if ($mode[$i] -gt $permissionsOct[$i]) {
			return $false # = less restrictive
		}
	}
	# check owning user and group
	return $item.User -eq $ownerUserName -and $item.Group -eq $ownerGroupName
}
[AuditTest] @{
	Id = "DSBD-014"
	Task = "Ensure /etc/passwd and /etc/passwd- have proper file permissions."
	Test = {
		$result = checkFilePermissions '/etc/passwd' '644' 'root' 'root'
		if (Test-Path -Path '/etc/passwd-' -PathType Leaf) {
			$result = $result -and (checkFilePermissions '/etc/passwd-' '644' 'root' 'root')
		}
		if ($result) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "The file permissions are not set correctly."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-015"
	Task = "Ensure /etc/shadow and /etc/shadow- have proper file permissions."
	Test = {
		$result = (checkFilePermissions '/etc/shadow' '640' 'root' 'root') -or (checkFilePermissions '/etc/shadow' '640' 'root' 'shadow')
		if (Test-Path -Path '/etc/shadow-' -PathType Leaf) {
			$resultDash = (checkFilePermissions '/etc/shadow-' '640' 'root' 'root') -or (checkFilePermissions '/etc/shadow-' '640' 'root' 'shadow')
			$result = $result -and $resultDash
		}
		if ($result) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "The file permissions are not set correctly."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-016"
	Task = "Ensure /etc/group and /etc/group- have proper file permissions."
	Test = {
		$result = checkFilePermissions '/etc/group' '644' 'root' 'root'
		if (Test-Path -Path '/etc/group-' -PathType Leaf) {
			$result = $result -and (checkFilePermissions '/etc/group-' '644' 'root' 'root')
		}
		if ($result) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "The file permissions are not set correctly."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-017"
	Task = "Ensure /etc/gshadow and /etc/gshadow- have proper file permissions."
	Test = {
		$result = (checkFilePermissions '/etc/gshadow' '640' 'root' 'root') -or (checkFilePermissions '/etc/gshadow' '640' 'root' 'shadow')
		if (Test-Path -Path '/etc/gshadow-' -PathType Leaf) {
			$resultDash = (checkFilePermissions '/etc/gshadow-' '640' 'root' 'root') -or (checkFilePermissions '/etc/gshadow-' '640' 'root' 'shadow')
			$result = $result -and $resultDash
		}
		if ($result) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "The file permissions are not set correctly."
				Status = "False"
			}
		}
	}
}
[AuditTest] @{
	Id = "DSBD-018"
	Task = "Ensure /etc/ssh/sshd_config has proper file permissions."
	Test = {
		$result = checkFilePermissions '/etc/ssh/sshd_config' '600' 'root' 'root'
		if ($result) {
			return @{
						Message = "Compliant"
						Status = "True"
			}
		} else {
			return @{
				Message = "The file permissions are not set correctly."
				Status = "False"
			}
		}
	}
}
