nip/src/nimpak/namespace_subsystem.nim

498 lines
16 KiB
Nim
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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