262 lines
8.4 KiB
Plaintext
262 lines
8.4 KiB
Plaintext
![]() |
# Remote Execution Library for Nushell Infrastructure
|
||
|
# Secure, audited remote script execution capabilities
|
||
|
|
||
|
# Execute a Nushell script remotely with security restrictions
|
||
|
export def nu-remote-exec [
|
||
|
script_path: string # Path to the Nushell script to execute
|
||
|
--readonly(-r) # Force read-only mode
|
||
|
--timeout(-t): int = 300 # Execution timeout in seconds
|
||
|
--audit(-a) # Enable audit logging
|
||
|
] -> record {
|
||
|
# Validate script path
|
||
|
if not ($script_path | path exists) {
|
||
|
return {
|
||
|
success: false
|
||
|
error: $"Script not found: ($script_path)"
|
||
|
output: ""
|
||
|
duration: 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Security checks
|
||
|
let allowed_paths = ($env.NUSHELL_ALLOWED_PATHS? | default "/tmp,/var/log,/proc,/sys" | split row ",")
|
||
|
let script_dir = ($script_path | path dirname)
|
||
|
|
||
|
if not ($allowed_paths | any {|path| $script_dir | str starts-with $path}) {
|
||
|
return {
|
||
|
success: false
|
||
|
error: $"Script path not in allowed directories: ($script_dir)"
|
||
|
output: ""
|
||
|
duration: 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Prepare execution environment
|
||
|
let start_time = (date now)
|
||
|
let session_id = (random uuid)
|
||
|
|
||
|
# Audit logging if enabled
|
||
|
if ($audit or ($env.NUSHELL_AUDIT_ENABLED? | default false)) {
|
||
|
let audit_entry = {
|
||
|
timestamp: ($start_time | format date "%Y-%m-%d %H:%M:%S")
|
||
|
session_id: $session_id
|
||
|
action: "remote-exec"
|
||
|
script: $script_path
|
||
|
readonly: $readonly
|
||
|
user: ($env.USER? | default "unknown")
|
||
|
hostname: ($env.HOSTNAME? | default "unknown")
|
||
|
}
|
||
|
$audit_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
|
||
|
}
|
||
|
|
||
|
# Build execution command with security flags
|
||
|
mut nu_args = ["--no-config-file"]
|
||
|
|
||
|
if $readonly or ($env.NUSHELL_READONLY_MODE? | default true) {
|
||
|
$nu_args = ($nu_args | append "--no-history")
|
||
|
}
|
||
|
|
||
|
# Set resource limits
|
||
|
let memory_limit = ($env.NUSHELL_MAX_MEMORY? | default "256MB")
|
||
|
let cpu_time = ($env.NUSHELL_MAX_CPU_TIME? | default "30s")
|
||
|
|
||
|
try {
|
||
|
# Execute with timeout and resource limits
|
||
|
let result = (timeout $"($timeout)s" nu ...$nu_args $script_path | complete)
|
||
|
let end_time = (date now)
|
||
|
let duration = (($end_time - $start_time) / 1ms | math round)
|
||
|
|
||
|
# Log completion
|
||
|
if ($audit or ($env.NUSHELL_AUDIT_ENABLED? | default false)) {
|
||
|
let completion_entry = {
|
||
|
timestamp: ($end_time | format date "%Y-%m-%d %H:%M:%S")
|
||
|
session_id: $session_id
|
||
|
action: "remote-exec-complete"
|
||
|
exit_code: $result.exit_code
|
||
|
duration_ms: $duration
|
||
|
output_lines: ($result.stdout | lines | length)
|
||
|
}
|
||
|
$completion_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
success: ($result.exit_code == 0)
|
||
|
error: $result.stderr
|
||
|
output: $result.stdout
|
||
|
duration: $duration
|
||
|
exit_code: $result.exit_code
|
||
|
session_id: $session_id
|
||
|
}
|
||
|
|
||
|
} catch { |err|
|
||
|
let end_time = (date now)
|
||
|
let duration = (($end_time - $start_time) / 1ms | math round)
|
||
|
|
||
|
# Log error
|
||
|
if ($audit or ($env.NUSHELL_AUDIT_ENABLED? | default false)) {
|
||
|
let error_entry = {
|
||
|
timestamp: ($end_time | format date "%Y-%m-%d %H:%M:%S")
|
||
|
session_id: $session_id
|
||
|
action: "remote-exec-error"
|
||
|
error: ($err | get msg)
|
||
|
duration_ms: $duration
|
||
|
}
|
||
|
$error_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
success: false
|
||
|
error: ($err | get msg)
|
||
|
output: ""
|
||
|
duration: $duration
|
||
|
exit_code: 1
|
||
|
session_id: $session_id
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Execute a command pipeline remotely with streaming output
|
||
|
export def nu-remote-stream [
|
||
|
command: string # Command to execute
|
||
|
--filter(-f): string # Optional filter expression
|
||
|
--format: string = "table" # Output format (table, json, yaml)
|
||
|
--lines(-l): int # Limit output lines
|
||
|
] -> any {
|
||
|
# Security validation
|
||
|
let blocked_commands = ($env.NUSHELL_BLOCKED_COMMANDS? | default "" | split row ",")
|
||
|
let cmd_parts = ($command | split row " ")
|
||
|
let cmd_name = ($cmd_parts | first)
|
||
|
|
||
|
if $cmd_name in $blocked_commands {
|
||
|
error make {msg: $"Command '($cmd_name)' is blocked for security reasons"}
|
||
|
}
|
||
|
|
||
|
# Build pipeline
|
||
|
mut pipeline = $command
|
||
|
|
||
|
if ($filter | is-not-empty) {
|
||
|
$pipeline = $"($pipeline) | ($filter)"
|
||
|
}
|
||
|
|
||
|
if ($lines | is-not-empty) {
|
||
|
$pipeline = $"($pipeline) | first ($lines)"
|
||
|
}
|
||
|
|
||
|
# Format output
|
||
|
match $format {
|
||
|
"json" => { $pipeline = $"($pipeline) | to json" }
|
||
|
"yaml" => { $pipeline = $"($pipeline) | to yaml" }
|
||
|
"csv" => { $pipeline = $"($pipeline) | to csv" }
|
||
|
_ => { $pipeline = $"($pipeline) | table" }
|
||
|
}
|
||
|
|
||
|
# Execute with audit
|
||
|
if ($env.NUSHELL_AUDIT_ENABLED? | default false) {
|
||
|
let audit_entry = {
|
||
|
timestamp: (date now | format date "%Y-%m-%d %H:%M:%S")
|
||
|
action: "remote-stream"
|
||
|
command: $command
|
||
|
filter: ($filter | default "")
|
||
|
format: $format
|
||
|
}
|
||
|
$audit_entry | to json | save -a ($env.NUSHELL_AUDIT_FILE? | default "/tmp/nushell-audit.log")
|
||
|
}
|
||
|
|
||
|
# Execute the pipeline
|
||
|
nu -c $pipeline
|
||
|
}
|
||
|
|
||
|
# Validate script security before execution
|
||
|
export def nu-validate-script [
|
||
|
script_path: string
|
||
|
] -> record {
|
||
|
if not ($script_path | path exists) {
|
||
|
return {valid: false, reason: "Script file not found"}
|
||
|
}
|
||
|
|
||
|
let content = (open $script_path)
|
||
|
let blocked_patterns = [
|
||
|
"rm -rf"
|
||
|
"sudo"
|
||
|
"su -"
|
||
|
"chmod 777"
|
||
|
"wget http://"
|
||
|
"curl http://"
|
||
|
"nc -"
|
||
|
"telnet"
|
||
|
"/dev/tcp"
|
||
|
"eval"
|
||
|
"exec"
|
||
|
]
|
||
|
|
||
|
for pattern in $blocked_patterns {
|
||
|
if ($content | str contains $pattern) {
|
||
|
return {
|
||
|
valid: false
|
||
|
reason: $"Script contains blocked pattern: ($pattern)"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Check for allowed paths only
|
||
|
let allowed_paths = ($env.NUSHELL_ALLOWED_PATHS? | default "/tmp,/var/log,/proc,/sys" | split row ",")
|
||
|
let path_accesses = ($content | find -r "/(etc|root|home|usr/bin)" | length)
|
||
|
|
||
|
if $path_accesses > 0 {
|
||
|
return {
|
||
|
valid: false
|
||
|
reason: "Script accesses restricted system paths"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return {valid: true, reason: "Script validation passed"}
|
||
|
}
|
||
|
|
||
|
# Health check for remote Nushell environment
|
||
|
export def nu-health-check [] -> record {
|
||
|
let start_time = (date now)
|
||
|
|
||
|
mut health = {
|
||
|
status: "healthy"
|
||
|
checks: {}
|
||
|
timestamp: ($start_time | format date "%Y-%m-%d %H:%M:%S")
|
||
|
}
|
||
|
|
||
|
# Check Nushell version
|
||
|
try {
|
||
|
let version = (version | get version)
|
||
|
$health.checks = ($health.checks | insert nushell_version {status: "ok", value: $version})
|
||
|
} catch {
|
||
|
$health.checks = ($health.checks | insert nushell_version {status: "error", value: "unknown"})
|
||
|
$health.status = "degraded"
|
||
|
}
|
||
|
|
||
|
# Check environment variables
|
||
|
let required_vars = ["NUSHELL_HOME", "NUSHELL_EXECUTION_MODE"]
|
||
|
for var in $required_vars {
|
||
|
if ($env | get -i $var | is-empty) {
|
||
|
$health.checks = ($health.checks | insert $"env_($var)" {status: "error", value: "missing"})
|
||
|
$health.status = "unhealthy"
|
||
|
} else {
|
||
|
$health.checks = ($health.checks | insert $"env_($var)" {status: "ok", value: ($env | get $var)})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Check disk space
|
||
|
try {
|
||
|
let disk_usage = (df -h | where Filesystem =~ "/" | first | get "Use%")
|
||
|
$health.checks = ($health.checks | insert disk_usage {status: "ok", value: $disk_usage})
|
||
|
} catch {
|
||
|
$health.checks = ($health.checks | insert disk_usage {status: "error", value: "unknown"})
|
||
|
}
|
||
|
|
||
|
# Check memory usage
|
||
|
try {
|
||
|
let mem_info = (free -m | lines | get 1 | split row " " | where $it != "" | get 2)
|
||
|
$health.checks = ($health.checks | insert memory_mb {status: "ok", value: $mem_info})
|
||
|
} catch {
|
||
|
$health.checks = ($health.checks | insert memory_mb {status: "error", value: "unknown"})
|
||
|
}
|
||
|
|
||
|
return $health
|
||
|
}
|