442 lines
16 KiB
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
|