modules/HomeLab.Security/HomeLab.Security.psm1

<#
.SYNOPSIS
    HomeLab Security Module
.DESCRIPTION
    Security functions for HomeLab including VPN certificate and client management
.NOTES
    Author: Jurie Smit
    Date: March 9, 2025
    Version: 1.0.0
#>


#region Initialization
# Save original preferences to restore later
$originalVerbosePreference = $VerbosePreference
$originalErrorActionPreference = $ErrorActionPreference
$originalWarningPreference = $WarningPreference

# Set preferences for module loading
$VerbosePreference = 'Continue'  # Show verbose output during loading
$ErrorActionPreference = 'Continue'  # Don't terminate on errors
$WarningPreference = 'Continue'  # Show all warnings

# Get module path for reference
$ModulePath = $PSScriptRoot
$ModuleName = (Get-Item $PSScriptRoot).BaseName

Write-Verbose "Starting $ModuleName module initialization from $ModulePath"
#endregion

#region Dependency Management
# Define required modules
$requiredModules = @('HomeLab.Core')
$missingModules = @()
$loadedModules = @()

Write-Host "Checking dependencies for $ModuleName..." -ForegroundColor Cyan

foreach ($module in $requiredModules) {
    Write-Host "Checking module: $module" -ForegroundColor Yellow
    
    # Check if module is already loaded
    if (Get-Module -Name $module) {
        Write-Host " ✓ Module $module is already loaded" -ForegroundColor Green
        $loadedModules += $module
        continue
    }
    
    $moduleLoaded = $false
    
    # Try to load from relative path first (sibling directory)
    $siblingPath = Join-Path -Path (Split-Path -Parent $ModulePath) -ChildPath $module -AdditionalChildPath "$module.psd1"
    
    if (Test-Path -Path $siblingPath) {
        Write-Host " → Found $module at $siblingPath" -ForegroundColor Yellow
        try {
            Import-Module -Name $siblingPath -Force -ErrorAction Stop
            Write-Host " ✓ Successfully loaded $module from sibling path" -ForegroundColor Green
            $moduleLoaded = $true
            $loadedModules += $module
        }
        catch {
            Write-Warning " ✗ Failed to load $module from sibling path: $($_.Exception.Message)"
        }
    }
    else {
        Write-Host " → Module not found at sibling path, checking PSModulePath" -ForegroundColor Yellow
    }
    
    # If not loaded from sibling path, try PSModulePath
    if (-not $moduleLoaded) {
        try {
            Import-Module -Name $module -Force -ErrorAction Stop
            Write-Host " ✓ Successfully loaded $module from PSModulePath" -ForegroundColor Green
            $moduleLoaded = $true
            $loadedModules += $module
        }
        catch {
            Write-Warning " ✗ Failed to load $module from PSModulePath: $($_.Exception.Message)"
        }
    }
    
    # If module couldn't be loaded, add to missing modules list
    if (-not $moduleLoaded) {
        $missingModules += $module
    }
}

# Display summary of dependency check
Write-Host "Dependency check summary:" -ForegroundColor Cyan
Write-Host " - Total required modules: $($requiredModules.Count)" -ForegroundColor Cyan
Write-Host " - Successfully loaded: $($loadedModules.Count)" -ForegroundColor $(if ($loadedModules.Count -eq $requiredModules.Count) { 'Green' } else { 'Yellow' })
Write-Host " - Missing modules: $($missingModules.Count)" -ForegroundColor $(if ($missingModules.Count -eq 0) { 'Green' } else { 'Red' })

if ($missingModules.Count -gt 0) {
    Write-Warning "Missing required modules: $($missingModules -join ', '). Some functionality may be limited."
}
#endregion

#region Module Variables
# Default VPN settings
$script:VpnCertificatesPath = Join-Path -Path $env:USERPROFILE -ChildPath "Documents\HomeLab\Certificates"
$script:VpnConfigPath = Join-Path -Path $env:USERPROFILE -ChildPath "Documents\HomeLab\VpnConfig"
$script:VpnDefaultValidity = 365 # Days
$script:VpnDefaultKeySize = 2048 # Bits
#endregion

#region Function Loading
# Load private functions (internal to the module)
$privatePath = Join-Path -Path $ModulePath -ChildPath 'Private'
$privateCount = 0

if (Test-Path -Path $privatePath) {
    $privateFiles = Get-ChildItem -Path "$privatePath\*.ps1" -ErrorAction SilentlyContinue
    
    Write-Host "Loading private functions from $privatePath..." -ForegroundColor Cyan
    Write-Verbose "Found $($privateFiles.Count) private function files"
    
    foreach ($file in $privateFiles) {
        try {
            . $file.FullName
            $privateCount++
            Write-Verbose "Loaded private function: $($file.BaseName)"
        }
        catch {
            Write-Warning "Failed to import private function $($file.BaseName): $($_.Exception.Message)"
        }
    }
    
    Write-Host " ✓ Loaded $privateCount private functions" -ForegroundColor Green
}
else {
    Write-Warning "Private directory not found: $privatePath"
}

# Load public functions (to be exported)
$publicPath = Join-Path -Path $ModulePath -ChildPath 'Public'
$exportFunctions = @()
$publicCount = 0

if (Test-Path -Path $publicPath) {
    $publicFiles = Get-ChildItem -Path "$publicPath\*.ps1" -ErrorAction SilentlyContinue
    
    Write-Host "Loading public functions from $publicPath..." -ForegroundColor Cyan
    Write-Verbose "Found $($publicFiles.Count) public function files"
    
    foreach ($file in $publicFiles) {
        try {
            . $file.FullName
            
            # Extract function name from file content
            $fileContent = Get-Content -Path $file.FullName -Raw
            if ($fileContent -match 'function\s+([A-Za-z0-9\-]+)') {
                $functionName = $matches[1]
                $exportFunctions += $functionName
                $publicCount++
                Write-Verbose "Added function to export list: $functionName"
            }
            else {
                Write-Warning "No function definition found in file: $($file.Name)"
            }
        }
        catch {
            Write-Warning "Failed to import public function $($file.BaseName): $($_.Exception.Message)"
        }
    }
    
    Write-Host " ✓ Loaded $publicCount public functions" -ForegroundColor Green
}
else {
    Write-Warning "Public directory not found: $publicPath"
}
#endregion

#region Module Finalization

# Display loading summary
Write-Host "$ModuleName module loaded successfully" -ForegroundColor Green

# Export the functions
if ($exportFunctions.Count -gt 0) {
    Export-ModuleMember -Function $exportFunctions
    
    # Display exported functions for verification
    Write-Host "Exported functions from $ModuleName module:" -ForegroundColor Cyan
    foreach ($function in $exportFunctions | Sort-Object) {
        Write-Host " - $function" -ForegroundColor Yellow
    }
}
else {
    Write-Warning "No functions exported from $ModuleName module"
}

# Export module variables
Export-ModuleMember -Variable 'VpnCertificatesPath', 'VpnConfigPath', 'VpnDefaultValidity', 'VpnDefaultKeySize'

# Display diagnostic information
Write-Host "`n===== DIAGNOSTIC INFORMATION =====" -ForegroundColor Magenta
Write-Host "Module path: $ModulePath" -ForegroundColor Magenta
Write-Host "Functions actually available in the module:" -ForegroundColor Magenta
$availableFunctions = Get-ChildItem function: | Where-Object { $_.ScriptBlock.File -like "*$ModulePath*" } | Select-Object -ExpandProperty Name
if ($availableFunctions) {
    foreach ($fn in $availableFunctions) {
        Write-Host " - $fn" -ForegroundColor Magenta
    }
} else {
    Write-Host " No functions found with this module path" -ForegroundColor Magenta
}

# Display loaded modules for verification
Write-Host "Currently loaded HomeLab modules:" -ForegroundColor Magenta
Get-Module | Where-Object { $_.Name -like "HomeLab.*" } | 
    Format-Table -Property Name, Version, Path -AutoSize

# Restore original preferences
$VerbosePreference = $originalVerbosePreference
$ErrorActionPreference = $originalErrorActionPreference
$WarningPreference = $originalWarningPreference
#endregion