From 4de6081a74018962ba3daa41d70d51f1d38e8987 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Wed, 26 Mar 2025 13:27:47 +0000 Subject: [PATCH] fix(installer): Improve Node.js binary detection, dependency management, and SNMPv3 fallback logic --- bin/nupst | 55 +++++++++++++++++++++++-- changelog.md | 9 +++++ package.json | 2 +- readme.md | 22 ++++++++-- setup.sh | 19 +++++++++ ts/00_commitinfo_data.ts | 2 +- ts/snmp/manager.ts | 86 +++++++++++++++++++++++++++++++++++----- 7 files changed, 177 insertions(+), 18 deletions(-) diff --git a/bin/nupst b/bin/nupst index a4f549b..9cb8916 100755 --- a/bin/nupst +++ b/bin/nupst @@ -22,16 +22,63 @@ fi # For debugging # echo "Project root: $PROJECT_ROOT" -# Set Node.js binary path directly -NODE_BIN="$PROJECT_ROOT/vendor/node-linux-x64/bin/node" +# Detect architecture and OS +ARCH=$(uname -m) +OS=$(uname -s) + +# Determine Node.js binary location based on architecture and OS +NODE_BIN="" +case "$OS" in + Linux) + case "$ARCH" in + x86_64) + NODE_BIN="$PROJECT_ROOT/vendor/node-linux-x64/bin/node" + ;; + aarch64|arm64) + NODE_BIN="$PROJECT_ROOT/vendor/node-linux-arm64/bin/node" + ;; + *) + # Use system Node as fallback for other architectures + if command -v node &> /dev/null; then + NODE_BIN="node" + echo "Using system Node.js installation for unsupported architecture: $ARCH" + fi + ;; + esac + ;; + Darwin) + case "$ARCH" in + x86_64) + NODE_BIN="$PROJECT_ROOT/vendor/node-darwin-x64/bin/node" + ;; + arm64) + NODE_BIN="$PROJECT_ROOT/vendor/node-darwin-arm64/bin/node" + ;; + *) + # Use system Node as fallback for other architectures + if command -v node &> /dev/null; then + NODE_BIN="node" + echo "Using system Node.js installation for unsupported architecture: $ARCH" + fi + ;; + esac + ;; + *) + # Use system Node as fallback for other operating systems + if command -v node &> /dev/null; then + NODE_BIN="node" + echo "Using system Node.js installation for unsupported OS: $OS" + fi + ;; +esac # If binary doesn't exist, try system Node as fallback -if [ ! -f "$NODE_BIN" ]; then +if [ -z "$NODE_BIN" ] || [ ! -f "$NODE_BIN" ]; then if command -v node &> /dev/null; then NODE_BIN="node" echo "Using system Node.js installation" else - echo "Error: Node.js binary not found at $NODE_BIN" + echo "Error: Node.js binary not found for $OS-$ARCH" echo "Please run the setup script or install Node.js manually." exit 1 fi diff --git a/changelog.md b/changelog.md index 98f9084..864409c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-03-26 - 2.5.2 - fix(installer) +Improve Node.js binary detection, dependency management, and SNMPv3 fallback logic + +- Enhanced bin/nupst to detect OS and architecture (Linux and Darwin) and fall back to system Node.js for unsupported platforms +- Moved net-snmp from devDependencies to dependencies in package.json +- Updated setup.sh to install production dependencies and handle installation errors gracefully +- Refined SNMPv3 user configuration and fallback mechanism in ts/snmp/manager.ts +- Revised README to clarify minimal runtime dependencies and secure SNMP features + ## 2025-03-25 - 2.5.1 - fix(snmp) Fix Eaton UPS support by updating power status OID and adjusting battery runtime conversion. diff --git a/package.json b/package.json index 49b6721..2223b89 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,9 @@ "author": "", "license": "MIT", "dependencies": { + "net-snmp": "3.20.0" }, "devDependencies": { - "net-snmp": "3.20.0", "@git.zone/tsbuild": "^2.3.2", "@git.zone/tsrun": "^1.3.3", "@git.zone/tstest": "^1.0.96", diff --git a/readme.md b/readme.md index a5f9d6d..bf99923 100644 --- a/readme.md +++ b/readme.md @@ -236,10 +236,10 @@ NUPST was designed with security in mind: ### Minimal Dependencies -- **Zero Runtime NPM Dependencies**: NUPST is built without any external NPM packages to minimize the attack surface and avoid supply chain risks. +- **Minimal Runtime Dependencies**: NUPST uses only one carefully selected NPM package (net-snmp) to minimize the attack surface and avoid supply chain risks while providing robust SNMP functionality. - **Self-contained Node.js**: NUPST ships with its own Node.js binary, isolated from the system's Node.js installation. This ensures: - No dependency on system Node.js versions - - Zero external libraries that could become compromised + - Minimal external libraries that could become compromised - Consistent, tested environment for execution - Reduced risk of dependency-based attacks @@ -247,14 +247,30 @@ NUPST was designed with security in mind: - **Privilege Separation**: Only specific commands that require elevated permissions (`enable`, `disable`, `update`) check for root access; all other functionality runs with minimal privileges. - **Limited Network Access**: NUPST only communicates with the UPS device over SNMP and contacts npmjs.org only to check for updates. -- **Secure SNMPv3 Support**: Supports encrypted authentication and privacy for secure communication with the UPS device. - **Isolated Execution**: The application runs in its working directory (`/opt/nupst`) or specified installation location, minimizing the impact on the rest of the system. +### SNMP Security Features + +- **SNMPv3 Support with Secure Authentication and Privacy**: + - Three security levels available: + - `noAuthNoPriv`: No authentication or encryption (basic access) + - `authNoPriv`: Authentication without encryption (verifies identity) + - `authPriv`: Full authentication and encryption (most secure) + - Authentication protocols: MD5 or SHA + - Privacy/encryption protocols: DES or AES + - Automatic fallback mechanisms for compatibility + - Context support for segmented SNMP deployments + - Configurable timeouts based on security level +- **Graceful degradation**: If authentication or privacy details are missing or invalid, NUPST will automatically fall back to a lower security level while logging appropriate warnings. +- **Interactive setup**: Guided setup process to properly configure SNMPv3 security settings with clear explanations of each security option. + ### Installation Security - The installation script can be reviewed before execution (`curl -sSL [url] | less`) - All setup scripts download only verified versions and check integrity - Installation is transparent and places files in standard locations (`/opt/nupst`, `/usr/local/bin`, `/etc/systemd/system`) +- Automatically detects platform architecture and OS for proper binary selection +- Installs production dependencies locally without requiring global npm packages ### Audit and Review diff --git a/setup.sh b/setup.sh index 5df2f27..70fb6b8 100644 --- a/setup.sh +++ b/setup.sh @@ -222,6 +222,25 @@ echo "dist_ts directory successfully downloaded from npm registry." # Make launcher script executable chmod +x "$SCRIPT_DIR/bin/nupst" +# Install production dependencies +echo "Installing production dependencies..." +"$SCRIPT_DIR/vendor/$NODE_DIR/bin/npm" --prefix "$SCRIPT_DIR" ci --only=production --no-audit --no-fund + +if [ $? -ne 0 ]; then + echo "Warning: Failed to install dependencies with 'npm ci'. Trying 'npm install'..." + "$SCRIPT_DIR/vendor/$NODE_DIR/bin/npm" --prefix "$SCRIPT_DIR" install --only=production --no-audit --no-fund + + if [ $? -ne 0 ]; then + echo "Error: Failed to install dependencies. NUPST may not function correctly." + echo "You can try to install dependencies manually by running:" + echo "cd $SCRIPT_DIR && npm install --only=production" + else + echo "Dependencies installed successfully with 'npm install'." + fi +else + echo "Dependencies installed successfully with 'npm ci'." +fi + echo "NUPST setup completed successfully." echo "You can now run NUPST using: $SCRIPT_DIR/bin/nupst" echo "To install NUPST globally, run: sudo ln -s $SCRIPT_DIR/bin/nupst /usr/local/bin/nupst" diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 051bf93..dea7a37 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/nupst', - version: '2.5.1', + version: '2.5.2', description: 'Node.js UPS Shutdown Tool for SNMP-enabled UPS devices' } diff --git a/ts/snmp/manager.ts b/ts/snmp/manager.ts index 488ca39..17d153b 100644 --- a/ts/snmp/manager.ts +++ b/ts/snmp/manager.ts @@ -105,13 +105,14 @@ export class NupstSnmp { retries: 2, // Number of retries timeout: config.timeout, transport: 'udp4', - idBitsSize: 32 + idBitsSize: 32, + context: config.context || '' }; // Set version based on config if (config.version === 1) { options.version = snmp.Version1; - } else if (config.version === 2 || config.version === 2) { + } else if (config.version === 2) { options.version = snmp.Version2c; } else { options.version = snmp.Version3; @@ -122,15 +123,82 @@ export class NupstSnmp { if (config.version === 3) { // For SNMPv3, we need to set up authentication and privacy - const user = { - name: config.username || '', - level: snmp.SecurityLevel[config.securityLevel || 'noAuthNoPriv'], - authProtocol: config.authProtocol ? snmp.AuthProtocols[config.authProtocol] : undefined, - authKey: config.authKey || '', - privProtocol: config.privProtocol ? snmp.PrivProtocols[config.privProtocol] : undefined, - privKey: config.privKey || '' + // For SNMPv3, we need a valid security level + const securityLevel = config.securityLevel || 'noAuthNoPriv'; + + // Create the user object with required structure for net-snmp + const user: any = { + name: config.username || '' }; + // Set security level + if (securityLevel === 'noAuthNoPriv') { + user.level = snmp.SecurityLevel.noAuthNoPriv; + } else if (securityLevel === 'authNoPriv') { + user.level = snmp.SecurityLevel.authNoPriv; + + // Set auth protocol - must provide both protocol and key + if (config.authProtocol && config.authKey) { + if (config.authProtocol === 'MD5') { + user.authProtocol = snmp.AuthProtocols.md5; + } else if (config.authProtocol === 'SHA') { + user.authProtocol = snmp.AuthProtocols.sha; + } + user.authKey = config.authKey; + } else { + // Fallback to noAuthNoPriv if auth details missing + user.level = snmp.SecurityLevel.noAuthNoPriv; + if (this.debug) { + console.log('Warning: Missing authProtocol or authKey, falling back to noAuthNoPriv'); + } + } + } else if (securityLevel === 'authPriv') { + user.level = snmp.SecurityLevel.authPriv; + + // Set auth protocol - must provide both protocol and key + if (config.authProtocol && config.authKey) { + if (config.authProtocol === 'MD5') { + user.authProtocol = snmp.AuthProtocols.md5; + } else if (config.authProtocol === 'SHA') { + user.authProtocol = snmp.AuthProtocols.sha; + } + user.authKey = config.authKey; + + // Set privacy protocol - must provide both protocol and key + if (config.privProtocol && config.privKey) { + if (config.privProtocol === 'DES') { + user.privProtocol = snmp.PrivProtocols.des; + } else if (config.privProtocol === 'AES') { + user.privProtocol = snmp.PrivProtocols.aes; + } + user.privKey = config.privKey; + } else { + // Fallback to authNoPriv if priv details missing + user.level = snmp.SecurityLevel.authNoPriv; + if (this.debug) { + console.log('Warning: Missing privProtocol or privKey, falling back to authNoPriv'); + } + } + } else { + // Fallback to noAuthNoPriv if auth details missing + user.level = snmp.SecurityLevel.noAuthNoPriv; + if (this.debug) { + console.log('Warning: Missing authProtocol or authKey, falling back to noAuthNoPriv'); + } + } + } + + if (this.debug) { + console.log('SNMPv3 user configuration:', { + name: user.name, + level: Object.keys(snmp.SecurityLevel).find(key => snmp.SecurityLevel[key] === user.level), + authProtocol: user.authProtocol ? 'Set' : 'Not Set', + authKey: user.authKey ? 'Set' : 'Not Set', + privProtocol: user.privProtocol ? 'Set' : 'Not Set', + privKey: user.privKey ? 'Set' : 'Not Set' + }); + } + session = snmp.createV3Session(config.host, user, options); } else { // For SNMPv1/v2c, we use the community string