nip/src/nimpak/profile_manager.nim

442 lines
16 KiB
Nim

# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.
## nimpak/profile_manager.nim
## Profile Manager for Nippels
##
## Manages security profiles and applies appropriate settings for different system roles.
## Supports profile loading, application, and customization.
##
## Requirements: 6.1-6.8
import std/[os, strutils, json, tables, options, times]
import utils/resultutils
import nippel_types
# =============================================================================
# Profile Overrides (Requirement 6.8)
# =============================================================================
type
ProfileOverrides* = object
## Per-Nippel profile customizations
isolationLevel*: Option[IsolationLevel]
desktopIntegration*: Option[bool]
networkAccess*: Option[NetworkAccessLevel]
resourceLimits*: Option[ResourceLimits]
auditingEnabled*: Option[bool]
ProfileError* = object of CatchableError
## Profile-specific errors
profileName*: string
context*: JsonNode
ProfileManager* = object
## Manages security profiles for Nippels
profilesDir*: string
customProfilesDir*: string
loadedProfiles*: Table[string, ProfileSettings]
# =============================================================================
# Profile Settings Definitions (Requirement 6.1-6.5)
# =============================================================================
proc getWorkstationProfile*(): ProfileSettings =
## Workstation profile: Standard isolation + desktop integration (Requirement 6.1)
## Suitable for desktop workstations with full GUI support
ProfileSettings(
isolationLevel: Standard,
desktopIntegration: true,
networkAccess: Full,
resourceLimits: ResourceLimits(
maxMemory: 8 * 1024 * 1024 * 1024, # 8GB
maxCpu: 0.9, # 90% CPU
maxDisk: 10 * 1024 * 1024 * 1024, # 10GB
maxProcesses: 200,
maxOpenFiles: 2048
),
auditingEnabled: false
)
proc getHomestationProfile*(): ProfileSettings =
## Homestation profile: Standard isolation + relaxed network (Requirement 6.2)
## Default profile for home users with balanced security and convenience
ProfileSettings(
isolationLevel: Standard,
desktopIntegration: true,
networkAccess: Relaxed,
resourceLimits: ResourceLimits(
maxMemory: 4 * 1024 * 1024 * 1024, # 4GB
maxCpu: 0.8, # 80% CPU
maxDisk: 5 * 1024 * 1024 * 1024, # 5GB
maxProcesses: 150,
maxOpenFiles: 1024
),
auditingEnabled: false
)
proc getSatelliteProfile*(): ProfileSettings =
## Satellite profile: Strict isolation + limited network (Requirement 6.3)
## For remote/mobile systems with enhanced security
ProfileSettings(
isolationLevel: Strict,
desktopIntegration: true,
networkAccess: Limited,
resourceLimits: ResourceLimits(
maxMemory: 2 * 1024 * 1024 * 1024, # 2GB
maxCpu: 0.7, # 70% CPU
maxDisk: 3 * 1024 * 1024 * 1024, # 3GB
maxProcesses: 100,
maxOpenFiles: 512
),
auditingEnabled: true
)
proc getNetworkIOTProfile*(): ProfileSettings =
## Network/IOT profile: Strict isolation + minimal resources (Requirement 6.4)
## For embedded devices and IoT systems
ProfileSettings(
isolationLevel: Strict,
desktopIntegration: false,
networkAccess: Limited,
resourceLimits: ResourceLimits(
maxMemory: 512 * 1024 * 1024, # 512MB
maxCpu: 0.5, # 50% CPU
maxDisk: 1 * 1024 * 1024 * 1024, # 1GB
maxProcesses: 50,
maxOpenFiles: 256
),
auditingEnabled: true
)
proc getServerProfile*(): ProfileSettings =
## Server profile: Strict isolation + no desktop + enhanced auditing (Requirement 6.5)
## For server environments with maximum security
ProfileSettings(
isolationLevel: Strict,
desktopIntegration: false,
networkAccess: Full,
resourceLimits: ResourceLimits(
maxMemory: 16 * 1024 * 1024 * 1024, # 16GB
maxCpu: 1.0, # 100% CPU
maxDisk: 50 * 1024 * 1024 * 1024, # 50GB
maxProcesses: 500,
maxOpenFiles: 4096
),
auditingEnabled: true
)
# =============================================================================
# Profile Manager Initialization
# =============================================================================
proc newProfileManager*(profilesDir: string = "", customProfilesDir: string = ""): ProfileManager =
## Create a new ProfileManager
let defaultProfilesDir = if profilesDir.len > 0: profilesDir
else: "/etc/nip/profiles/security"
let defaultCustomDir = if customProfilesDir.len > 0: customProfilesDir
else: getHomeDir() / ".config" / "nip" / "profiles" / "security"
ProfileManager(
profilesDir: defaultProfilesDir,
customProfilesDir: defaultCustomDir,
loadedProfiles: initTable[string, ProfileSettings]()
)
# =============================================================================
# Profile Loading (Requirement 6.7)
# =============================================================================
proc loadProfile*(profile: SecurityProfile): ProfileSettings =
## Load profile settings for a security profile (Requirement 6.7)
case profile:
of Workstation:
getWorkstationProfile()
of Homestation:
getHomestationProfile()
of Satellite:
getSatelliteProfile()
of NetworkIOT:
getNetworkIOTProfile()
of Server:
getServerProfile()
proc loadProfile*(manager: var ProfileManager, profile: SecurityProfile): ProfileSettings =
## Load profile settings through ProfileManager
let profileName = $profile
if profileName in manager.loadedProfiles:
return manager.loadedProfiles[profileName]
let settings = loadProfile(profile)
manager.loadedProfiles[profileName] = settings
return settings
proc loadProfileFromFile*(path: string): Result[ProfileSettings, string] =
## Load profile settings from a custom file
try:
if not fileExists(path):
return err[ProfileSettings]("Profile file not found: " & path)
let config = parseJson(readFile(path))
# Parse isolation level
let isolationStr = config["isolation"].getStr("Standard")
let isolation = parseEnum[IsolationLevel](isolationStr)
# Parse network access
let networkStr = config["networkAccess"].getStr("Relaxed")
let networkAccess = parseEnum[NetworkAccessLevel](networkStr)
# Parse resource limits
let limits = config["resourceLimits"]
let resourceLimits = ResourceLimits(
maxMemory: limits["maxMemory"].getInt(4 * 1024 * 1024 * 1024),
maxCpu: limits["maxCpu"].getFloat(0.8),
maxDisk: limits["maxDisk"].getInt(5 * 1024 * 1024 * 1024),
maxProcesses: limits["maxProcesses"].getInt(150),
maxOpenFiles: limits["maxOpenFiles"].getInt(1024)
)
let settings = ProfileSettings(
isolationLevel: isolation,
desktopIntegration: config["desktopIntegration"].getBool(true),
networkAccess: networkAccess,
resourceLimits: resourceLimits,
auditingEnabled: config["auditingEnabled"].getBool(false)
)
return ok[ProfileSettings](settings)
except Exception as e:
return err[ProfileSettings]("Failed to load profile: " & e.msg)
proc loadProfileFromFile*(manager: var ProfileManager, path: string): Result[ProfileSettings, string] =
## Load profile settings from file through ProfileManager
loadProfileFromFile(path)
# =============================================================================
# Profile Application (Requirement 6.7)
# =============================================================================
proc applyProfile*(nippel: var Nippel, settings: ProfileSettings): Result[bool, string] =
## Apply profile settings to a Nippel (Requirement 6.7)
## Returns true on success
try:
# Apply isolation level
nippel.isolationLevel = settings.isolationLevel
nippel.profileSettings.isolationLevel = settings.isolationLevel
# Apply desktop integration
nippel.profileSettings.desktopIntegration = settings.desktopIntegration
# Apply network access settings
nippel.profileSettings.networkAccess = settings.networkAccess
# Apply resource limits
nippel.profileSettings.resourceLimits = settings.resourceLimits
# Apply auditing settings
nippel.profileSettings.auditingEnabled = settings.auditingEnabled
# Update last used timestamp
nippel.lastUsed = now()
# Save updated configuration
let cellConfig = %*{
"nippel": {
"name": nippel.name,
"id": nippel.id,
"version": nippel.version,
"created": $nippel.created,
"lastUsed": $nippel.lastUsed
},
"profile": {
"type": $nippel.profile,
"isolation": $nippel.isolationLevel,
"desktopIntegration": nippel.profileSettings.desktopIntegration,
"networkAccess": $nippel.profileSettings.networkAccess,
"auditingEnabled": nippel.profileSettings.auditingEnabled
},
"resourceLimits": {
"maxMemory": nippel.profileSettings.resourceLimits.maxMemory,
"maxCpu": nippel.profileSettings.resourceLimits.maxCpu,
"maxDisk": nippel.profileSettings.resourceLimits.maxDisk,
"maxProcesses": nippel.profileSettings.resourceLimits.maxProcesses,
"maxOpenFiles": nippel.profileSettings.resourceLimits.maxOpenFiles
},
"paths": {
"root": nippel.cellRoot,
"data": nippel.xdgDirs.dataHome,
"config": nippel.xdgDirs.configHome,
"cache": nippel.xdgDirs.cacheHome,
"state": nippel.xdgDirs.stateHome,
"runtime": nippel.xdgDirs.runtimeDir
},
"storage": {
"merkle_root": nippel.merkleRoot,
"cas_entries": nippel.casEntries.len,
"total_size": 0
},
"network": {
"utcp_address": formatUTCPAddress(nippel.utcpAddress)
},
"packages": newJArray()
}
writeFile(nippel.cellRoot / "cell.json", cellConfig.pretty())
echo "✅ Applied profile settings to Nippel: ", nippel.name
echo " Isolation: ", nippel.isolationLevel
echo " Desktop Integration: ", nippel.profileSettings.desktopIntegration
echo " Network Access: ", nippel.profileSettings.networkAccess
echo " Auditing: ", nippel.profileSettings.auditingEnabled
return ok(true)
except Exception as e:
return err[bool]("Failed to apply profile: " & e.msg)
# =============================================================================
# Profile Customization (Requirement 6.8)
# =============================================================================
proc customizeProfile*(nippel: var Nippel, overrides: ProfileOverrides): Result[bool, string] =
## Apply per-Nippel profile overrides (Requirement 6.8)
## Returns true on success
try:
var modified = false
# Apply isolation level override
if overrides.isolationLevel.isSome:
nippel.isolationLevel = overrides.isolationLevel.get()
nippel.profileSettings.isolationLevel = overrides.isolationLevel.get()
modified = true
echo " Override: Isolation level -> ", nippel.isolationLevel
# Apply desktop integration override
if overrides.desktopIntegration.isSome:
nippel.profileSettings.desktopIntegration = overrides.desktopIntegration.get()
modified = true
echo " Override: Desktop integration -> ", nippel.profileSettings.desktopIntegration
# Apply network access override
if overrides.networkAccess.isSome:
nippel.profileSettings.networkAccess = overrides.networkAccess.get()
modified = true
echo " Override: Network access -> ", nippel.profileSettings.networkAccess
# Apply resource limits override
if overrides.resourceLimits.isSome:
nippel.profileSettings.resourceLimits = overrides.resourceLimits.get()
modified = true
echo " Override: Resource limits updated"
# Apply auditing override
if overrides.auditingEnabled.isSome:
nippel.profileSettings.auditingEnabled = overrides.auditingEnabled.get()
modified = true
echo " Override: Auditing -> ", nippel.profileSettings.auditingEnabled
if not modified:
echo " No overrides applied"
return ok(true)
# Update last used timestamp
nippel.lastUsed = now()
# Save updated configuration
let cellConfig = %*{
"nippel": {
"name": nippel.name,
"id": nippel.id,
"version": nippel.version,
"created": $nippel.created,
"lastUsed": $nippel.lastUsed
},
"profile": {
"type": $nippel.profile,
"isolation": $nippel.isolationLevel,
"desktopIntegration": nippel.profileSettings.desktopIntegration,
"networkAccess": $nippel.profileSettings.networkAccess,
"auditingEnabled": nippel.profileSettings.auditingEnabled
},
"resourceLimits": {
"maxMemory": nippel.profileSettings.resourceLimits.maxMemory,
"maxCpu": nippel.profileSettings.resourceLimits.maxCpu,
"maxDisk": nippel.profileSettings.resourceLimits.maxDisk,
"maxProcesses": nippel.profileSettings.resourceLimits.maxProcesses,
"maxOpenFiles": nippel.profileSettings.resourceLimits.maxOpenFiles
},
"paths": {
"root": nippel.cellRoot,
"data": nippel.xdgDirs.dataHome,
"config": nippel.xdgDirs.configHome,
"cache": nippel.xdgDirs.cacheHome,
"state": nippel.xdgDirs.stateHome,
"runtime": nippel.xdgDirs.runtimeDir
},
"storage": {
"merkle_root": nippel.merkleRoot,
"cas_entries": nippel.casEntries.len,
"total_size": 0
},
"network": {
"utcp_address": formatUTCPAddress(nippel.utcpAddress)
},
"packages": newJArray()
}
writeFile(nippel.cellRoot / "cell.json", cellConfig.pretty())
echo "✅ Applied profile customizations to Nippel: ", nippel.name
return ok(true)
except Exception as e:
return err[bool]("Failed to customize profile: " & e.msg)
# =============================================================================
# Profile Information
# =============================================================================
proc getProfileInfo*(profile: SecurityProfile): string =
## Get human-readable information about a profile
let settings = loadProfile(profile)
result = "Profile: " & $profile & "\n"
result.add(" Isolation: " & $settings.isolationLevel & "\n")
result.add(" Desktop Integration: " & $settings.desktopIntegration & "\n")
result.add(" Network Access: " & $settings.networkAccess & "\n")
result.add(" Auditing: " & $settings.auditingEnabled & "\n")
result.add(" Resource Limits:\n")
result.add(" Max Memory: " & $(settings.resourceLimits.maxMemory div (1024 * 1024)) & " MB\n")
result.add(" Max CPU: " & $(settings.resourceLimits.maxCpu * 100) & "%\n")
result.add(" Max Disk: " & $(settings.resourceLimits.maxDisk div (1024 * 1024)) & " MB\n")
result.add(" Max Processes: " & $settings.resourceLimits.maxProcesses & "\n")
result.add(" Max Open Files: " & $settings.resourceLimits.maxOpenFiles)
proc listAvailableProfiles*(): seq[string] =
## List all available security profiles
result = @[
"Workstation - Standard isolation + desktop integration",
"Homestation - Standard isolation + relaxed network (default)",
"Satellite - Strict isolation + limited network (remote/mobile)",
"NetworkIOT - Strict isolation + minimal resources (embedded)",
"Server - Strict isolation + no desktop + enhanced auditing"
]
# =============================================================================
# Exports
# =============================================================================
export ProfileOverrides, ProfileError, ProfileManager
export getWorkstationProfile, getHomestationProfile, getSatelliteProfile
export getNetworkIOTProfile, getServerProfile
export newProfileManager, loadProfile, loadProfileFromFile
export applyProfile, customizeProfile
export getProfileInfo, listAvailableProfiles