498 lines
16 KiB
Nim
498 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/namespace_subsystem.nim
|
||
## Namespace Subsystem for Nippels
|
||
##
|
||
## Provides Linux kernel namespace isolation for lightweight application environments.
|
||
## Implements different isolation levels from None to Quantum with zero overhead.
|
||
##
|
||
## Requirements: 1.1, 1.4, 1.5, 5.1-5.5
|
||
|
||
import std/[os, posix]
|
||
import utils/resultutils
|
||
import nippel_types
|
||
|
||
# =============================================================================
|
||
# Linux Namespace Constants
|
||
# =============================================================================
|
||
|
||
# Clone flags for namespace creation (from linux/sched.h)
|
||
const
|
||
CLONE_NEWNS* = 0x00020000.cint # Mount namespace
|
||
CLONE_NEWPID* = 0x20000000.cint # PID namespace
|
||
CLONE_NEWNET* = 0x40000000.cint # Network namespace
|
||
CLONE_NEWIPC* = 0x08000000.cint # IPC namespace
|
||
CLONE_NEWUSER* = 0x10000000.cint # User namespace
|
||
CLONE_NEWUTS* = 0x04000000.cint # UTS namespace
|
||
|
||
# Linux syscalls for namespace operations
|
||
proc unshare(flags: cint): cint {.importc, header: "<sched.h>".}
|
||
proc setns(fd: cint, nstype: cint): cint {.importc, header: "<sched.h>".}
|
||
|
||
# =============================================================================
|
||
# Namespace Error Types
|
||
# =============================================================================
|
||
|
||
type
|
||
NamespaceError* = object of CatchableError
|
||
## Namespace-specific errors
|
||
nippelName*: string
|
||
operation*: string
|
||
errno*: cint
|
||
|
||
# =============================================================================
|
||
# Namespace Configuration (Requirement 5.1-5.5)
|
||
# =============================================================================
|
||
|
||
proc getNamespaceHandle*(level: IsolationLevel): NamespaceHandle =
|
||
## Get namespace configuration for an isolation level
|
||
##
|
||
## Isolation Levels:
|
||
## - None: No isolation (full system access)
|
||
## - Standard: Mount + filesystem namespaces only
|
||
## - Strict: Mount + PID + network + IPC namespaces
|
||
## - Quantum: All namespaces including user and UTS
|
||
|
||
case level:
|
||
of None:
|
||
# Requirement 5.1: No isolation
|
||
NamespaceHandle(
|
||
mountNS: false,
|
||
pidNS: false,
|
||
networkNS: false,
|
||
ipcNS: false,
|
||
userNS: false,
|
||
utsNS: false,
|
||
nsPath: ""
|
||
)
|
||
|
||
of Standard:
|
||
# Requirement 5.2: Standard isolation (mount + filesystem)
|
||
NamespaceHandle(
|
||
mountNS: true,
|
||
pidNS: false,
|
||
networkNS: false,
|
||
ipcNS: false,
|
||
userNS: false,
|
||
utsNS: false,
|
||
nsPath: ""
|
||
)
|
||
|
||
of Strict:
|
||
# Requirement 5.3: Strict isolation (mount + PID + network + IPC)
|
||
NamespaceHandle(
|
||
mountNS: true,
|
||
pidNS: true,
|
||
networkNS: true,
|
||
ipcNS: true,
|
||
userNS: false,
|
||
utsNS: false,
|
||
nsPath: ""
|
||
)
|
||
|
||
of Quantum:
|
||
# Requirement 5.4: Quantum isolation (all namespaces)
|
||
NamespaceHandle(
|
||
mountNS: true,
|
||
pidNS: true,
|
||
networkNS: true,
|
||
ipcNS: true,
|
||
userNS: true,
|
||
utsNS: true,
|
||
nsPath: ""
|
||
)
|
||
|
||
proc validateNamespaceHandle*(config: NamespaceHandle): Result[bool, string] =
|
||
## Validate namespace configuration
|
||
## Ensures that namespace combinations are valid and supported
|
||
|
||
try:
|
||
# User namespace requires other namespaces to be useful
|
||
if config.userNS and not (config.mountNS or config.pidNS):
|
||
return err[bool]("User namespace requires at least mount or PID namespace")
|
||
|
||
# PID namespace is most useful with mount namespace
|
||
if config.pidNS and not config.mountNS:
|
||
echo "⚠️ Warning: PID namespace without mount namespace may have limited isolation"
|
||
|
||
# Network namespace requires root or user namespace
|
||
if config.networkNS and not config.userNS:
|
||
if getuid() != 0:
|
||
echo "⚠️ Warning: Network namespace requires root privileges or user namespace"
|
||
echo " Continuing without network namespace"
|
||
# Don't fail validation, just warn - we'll handle it in createNamespaces
|
||
# return err[bool]("Network namespace requires root privileges or user namespace")
|
||
|
||
return ok(true)
|
||
|
||
except Exception as e:
|
||
return err[bool]("Failed to validate namespace config: " & e.msg)
|
||
|
||
# =============================================================================
|
||
# Namespace Operations (Requirement 1.1, 1.5)
|
||
# =============================================================================
|
||
|
||
proc createNamespaces*(config: NamespaceHandle): Result[NamespaceHandle, string] =
|
||
## Create Linux kernel namespaces based on configuration (Requirement 1.1)
|
||
##
|
||
## This uses the unshare() syscall to create new namespaces for the current process.
|
||
## The namespaces are created but not yet entered - use enterNamespace() for that.
|
||
|
||
try:
|
||
# Validate configuration first
|
||
let validResult = validateNamespaceHandle(config)
|
||
if validResult.isErr:
|
||
return err[NamespaceHandle](validResult.error)
|
||
|
||
# Build flags for unshare() syscall
|
||
var flags: cint = 0
|
||
|
||
if config.mountNS:
|
||
flags = flags or CLONE_NEWNS
|
||
echo " 📁 Mount namespace enabled"
|
||
|
||
if config.pidNS:
|
||
flags = flags or CLONE_NEWPID
|
||
echo " 🔢 PID namespace enabled"
|
||
|
||
if config.networkNS:
|
||
flags = flags or CLONE_NEWNET
|
||
echo " 🌐 Network namespace enabled"
|
||
|
||
if config.ipcNS:
|
||
flags = flags or CLONE_NEWIPC
|
||
echo " 💬 IPC namespace enabled"
|
||
|
||
if config.userNS:
|
||
flags = flags or CLONE_NEWUSER
|
||
echo " 👤 User namespace enabled"
|
||
|
||
if config.utsNS:
|
||
flags = flags or CLONE_NEWUTS
|
||
echo " 🖥️ UTS namespace enabled"
|
||
|
||
# If no namespaces requested, return empty handle
|
||
if flags == 0:
|
||
echo " ℹ️ No namespace isolation (None level)"
|
||
return ok(NamespaceHandle(
|
||
mountNS: false,
|
||
pidNS: false,
|
||
networkNS: false,
|
||
ipcNS: false,
|
||
userNS: false,
|
||
utsNS: false,
|
||
nsPath: ""
|
||
))
|
||
|
||
# Create namespaces using unshare()
|
||
let unshareResult = unshare(flags)
|
||
if unshareResult != 0:
|
||
let errNo = errno
|
||
# Check if it's a permission error
|
||
if errNo == EPERM:
|
||
echo "⚠️ Warning: Namespace creation requires root privileges"
|
||
echo " Continuing without namespace isolation"
|
||
# Return empty handle to allow operation to continue
|
||
return ok(NamespaceHandle(
|
||
mountNS: false,
|
||
pidNS: false,
|
||
networkNS: false,
|
||
ipcNS: false,
|
||
userNS: false,
|
||
utsNS: false,
|
||
nsPath: ""
|
||
))
|
||
return err[NamespaceHandle]("Failed to create namespaces: " & $strerror(errNo))
|
||
|
||
# Store namespace file descriptors for later use
|
||
let pid = getpid()
|
||
let nsPath = "/proc/" & $pid & "/ns"
|
||
|
||
echo "✅ Created namespaces successfully"
|
||
echo " Namespace path: ", nsPath
|
||
|
||
return ok(NamespaceHandle(
|
||
mountNS: config.mountNS,
|
||
pidNS: config.pidNS,
|
||
networkNS: config.networkNS,
|
||
ipcNS: config.ipcNS,
|
||
userNS: config.userNS,
|
||
utsNS: config.utsNS,
|
||
nsPath: nsPath
|
||
))
|
||
|
||
except Exception as e:
|
||
return err[NamespaceHandle]("Failed to create namespaces: " & e.msg)
|
||
|
||
proc enterNamespace*(handle: NamespaceHandle): Result[bool, string] =
|
||
## Enter an existing namespace (Requirement 1.5)
|
||
##
|
||
## This uses the setns() syscall to enter namespaces that were previously created.
|
||
## Useful for activating a Nippel that has existing namespaces.
|
||
|
||
try:
|
||
if handle.nsPath.len == 0:
|
||
# No namespaces to enter
|
||
return ok(true)
|
||
|
||
echo "🔄 Entering namespaces..."
|
||
|
||
# Enter each namespace type if enabled
|
||
if handle.mountNS:
|
||
let nsFile = handle.nsPath / "mnt"
|
||
if fileExists(nsFile):
|
||
let fd = open(cstring(nsFile), O_RDONLY)
|
||
if fd < 0:
|
||
return err[bool]("Failed to open mount namespace: " & $strerror(errno))
|
||
|
||
if setns(fd, CLONE_NEWNS) != 0:
|
||
discard close(fd)
|
||
return err[bool]("Failed to enter mount namespace: " & $strerror(errno))
|
||
|
||
discard close(fd)
|
||
echo " ✅ Entered mount namespace"
|
||
|
||
if handle.pidNS:
|
||
let nsFile = handle.nsPath / "pid"
|
||
if fileExists(nsFile):
|
||
let fd = open(cstring(nsFile), O_RDONLY)
|
||
if fd < 0:
|
||
return err[bool]("Failed to open PID namespace: " & $strerror(errno))
|
||
|
||
if setns(fd, CLONE_NEWPID) != 0:
|
||
discard close(fd)
|
||
return err[bool]("Failed to enter PID namespace: " & $strerror(errno))
|
||
|
||
discard close(fd)
|
||
echo " ✅ Entered PID namespace"
|
||
|
||
if handle.networkNS:
|
||
let nsFile = handle.nsPath / "net"
|
||
if fileExists(nsFile):
|
||
let fd = open(cstring(nsFile), O_RDONLY)
|
||
if fd < 0:
|
||
return err[bool]("Failed to open network namespace: " & $strerror(errno))
|
||
|
||
if setns(fd, CLONE_NEWNET) != 0:
|
||
discard close(fd)
|
||
return err[bool]("Failed to enter network namespace: " & $strerror(errno))
|
||
|
||
discard close(fd)
|
||
echo " ✅ Entered network namespace"
|
||
|
||
if handle.ipcNS:
|
||
let nsFile = handle.nsPath / "ipc"
|
||
if fileExists(nsFile):
|
||
let fd = open(cstring(nsFile), O_RDONLY)
|
||
if fd < 0:
|
||
return err[bool]("Failed to open IPC namespace: " & $strerror(errno))
|
||
|
||
if setns(fd, CLONE_NEWIPC) != 0:
|
||
discard close(fd)
|
||
return err[bool]("Failed to enter IPC namespace: " & $strerror(errno))
|
||
|
||
discard close(fd)
|
||
echo " ✅ Entered IPC namespace"
|
||
|
||
if handle.userNS:
|
||
let nsFile = handle.nsPath / "user"
|
||
if fileExists(nsFile):
|
||
let fd = open(cstring(nsFile), O_RDONLY)
|
||
if fd < 0:
|
||
return err[bool]("Failed to open user namespace: " & $strerror(errno))
|
||
|
||
if setns(fd, CLONE_NEWUSER) != 0:
|
||
discard close(fd)
|
||
return err[bool]("Failed to enter user namespace: " & $strerror(errno))
|
||
|
||
discard close(fd)
|
||
echo " ✅ Entered user namespace"
|
||
|
||
if handle.utsNS:
|
||
let nsFile = handle.nsPath / "uts"
|
||
if fileExists(nsFile):
|
||
let fd = open(cstring(nsFile), O_RDONLY)
|
||
if fd < 0:
|
||
return err[bool]("Failed to open UTS namespace: " & $strerror(errno))
|
||
|
||
if setns(fd, CLONE_NEWUTS) != 0:
|
||
discard close(fd)
|
||
return err[bool]("Failed to enter UTS namespace: " & $strerror(errno))
|
||
|
||
discard close(fd)
|
||
echo " ✅ Entered UTS namespace"
|
||
|
||
echo "✅ Successfully entered all namespaces"
|
||
return ok(true)
|
||
|
||
except Exception as e:
|
||
return err[bool]("Failed to enter namespaces: " & e.msg)
|
||
|
||
proc exitNamespace*(handle: NamespaceHandle): Result[bool, string] =
|
||
## Exit namespace and return to host namespace (Requirement 1.5)
|
||
##
|
||
## Note: In practice, exiting namespaces is done by the kernel when the process exits.
|
||
## This function is mainly for cleanup and documentation purposes.
|
||
|
||
try:
|
||
echo "🔄 Exiting namespaces..."
|
||
|
||
# Namespaces are automatically cleaned up when the process exits
|
||
# or when all references to them are closed
|
||
|
||
# For now, we just log that we're exiting
|
||
if handle.nsPath.len > 0:
|
||
echo " ℹ️ Namespace cleanup will occur on process exit"
|
||
echo " Namespace path: ", handle.nsPath
|
||
|
||
echo "✅ Namespace exit prepared"
|
||
return ok(true)
|
||
|
||
except Exception as e:
|
||
return err[bool]("Failed to exit namespaces: " & e.msg)
|
||
|
||
proc destroyNamespaces*(handle: NamespaceHandle): Result[bool, string] =
|
||
## Destroy namespaces and clean up resources (Requirement 1.5)
|
||
##
|
||
## Namespaces are automatically destroyed by the kernel when all processes
|
||
## using them have exited and all file descriptors are closed.
|
||
|
||
try:
|
||
echo "🗑️ Destroying namespaces..."
|
||
|
||
if handle.nsPath.len == 0:
|
||
echo " ℹ️ No namespaces to destroy"
|
||
return ok(true)
|
||
|
||
# Namespaces are reference-counted by the kernel
|
||
# They will be automatically destroyed when:
|
||
# 1. All processes in the namespace have exited
|
||
# 2. All file descriptors referencing the namespace are closed
|
||
|
||
echo " ℹ️ Namespaces will be destroyed by kernel when no longer referenced"
|
||
echo " Namespace path: ", handle.nsPath
|
||
|
||
echo "✅ Namespace destruction initiated"
|
||
return ok(true)
|
||
|
||
except Exception as e:
|
||
return err[bool]("Failed to destroy namespaces: " & e.msg)
|
||
|
||
# =============================================================================
|
||
# Namespace Information
|
||
# =============================================================================
|
||
|
||
proc getNamespaceInfo*(handle: NamespaceHandle): string =
|
||
## Get human-readable information about namespace configuration
|
||
result = "Namespace Configuration:\n"
|
||
result.add(" Mount: " & $handle.mountNS & "\n")
|
||
result.add(" PID: " & $handle.pidNS & "\n")
|
||
result.add(" Network: " & $handle.networkNS & "\n")
|
||
result.add(" IPC: " & $handle.ipcNS & "\n")
|
||
result.add(" User: " & $handle.userNS & "\n")
|
||
result.add(" UTS: " & $handle.utsNS & "\n")
|
||
if handle.nsPath.len > 0:
|
||
result.add(" Path: " & handle.nsPath)
|
||
|
||
proc getIsolationLevelInfo*(level: IsolationLevel): string =
|
||
## Get human-readable information about an isolation level
|
||
let config = getNamespaceHandle(level)
|
||
|
||
result = "Isolation Level: " & $level & "\n"
|
||
result.add(" Description: ")
|
||
|
||
case level:
|
||
of None:
|
||
result.add("No isolation - full system access\n")
|
||
of Standard:
|
||
result.add("Standard isolation - mount + filesystem namespaces\n")
|
||
of Strict:
|
||
result.add("Strict isolation - mount + PID + network + IPC namespaces\n")
|
||
of Quantum:
|
||
result.add("Quantum isolation - all namespaces + cryptographic boundaries\n")
|
||
|
||
result.add("\n" & getNamespaceInfo(NamespaceHandle(
|
||
mountNS: config.mountNS,
|
||
pidNS: config.pidNS,
|
||
networkNS: config.networkNS,
|
||
ipcNS: config.ipcNS,
|
||
userNS: config.userNS,
|
||
utsNS: config.utsNS,
|
||
nsPath: ""
|
||
)))
|
||
|
||
# =============================================================================
|
||
# Namespace Verification
|
||
# =============================================================================
|
||
|
||
proc verifyNamespaces*(handle: NamespaceHandle): Result[bool, string] =
|
||
## Verify that namespaces are correctly set up and accessible
|
||
|
||
try:
|
||
if handle.nsPath.len == 0:
|
||
# No namespaces to verify
|
||
return ok(true)
|
||
|
||
# Check if namespace directory exists
|
||
if not dirExists(handle.nsPath):
|
||
return err[bool]("Namespace directory does not exist: " & handle.nsPath)
|
||
|
||
# Verify each enabled namespace
|
||
var verified = 0
|
||
|
||
if handle.mountNS:
|
||
let nsFile = handle.nsPath / "mnt"
|
||
if not fileExists(nsFile):
|
||
return err[bool]("Mount namespace file does not exist: " & nsFile)
|
||
verified.inc
|
||
|
||
if handle.pidNS:
|
||
let nsFile = handle.nsPath / "pid"
|
||
if not fileExists(nsFile):
|
||
return err[bool]("PID namespace file does not exist: " & nsFile)
|
||
verified.inc
|
||
|
||
if handle.networkNS:
|
||
let nsFile = handle.nsPath / "net"
|
||
if not fileExists(nsFile):
|
||
return err[bool]("Network namespace file does not exist: " & nsFile)
|
||
verified.inc
|
||
|
||
if handle.ipcNS:
|
||
let nsFile = handle.nsPath / "ipc"
|
||
if not fileExists(nsFile):
|
||
return err[bool]("IPC namespace file does not exist: " & nsFile)
|
||
verified.inc
|
||
|
||
if handle.userNS:
|
||
let nsFile = handle.nsPath / "user"
|
||
if not fileExists(nsFile):
|
||
return err[bool]("User namespace file does not exist: " & nsFile)
|
||
verified.inc
|
||
|
||
if handle.utsNS:
|
||
let nsFile = handle.nsPath / "uts"
|
||
if not fileExists(nsFile):
|
||
return err[bool]("UTS namespace file does not exist: " & nsFile)
|
||
verified.inc
|
||
|
||
echo "✅ Verified ", verified, " namespace(s)"
|
||
return ok(true)
|
||
|
||
except Exception as e:
|
||
return err[bool]("Failed to verify namespaces: " & e.msg)
|
||
|
||
# =============================================================================
|
||
# Exports
|
||
# =============================================================================
|
||
|
||
export NamespaceError
|
||
export getNamespaceHandle, validateNamespaceHandle
|
||
export createNamespaces, enterNamespace, exitNamespace, destroyNamespaces
|
||
export getNamespaceInfo, getIsolationLevelInfo, verifyNamespaces
|