mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Fix some scripting issues
This commit is contained in:
parent
2158478894
commit
97bbc77162
11 changed files with 129 additions and 53 deletions
11
installer/README.md
Normal file
11
installer/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Safe Chain Installer - WIP
|
||||||
|
|
||||||
|
This directory contains the build scripts and resources for creating standalone Safe Chain installers for different platforms.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The installer bundles the Safe Chain Node.js application into a standalone binary using [@yao-pkg/pkg](https://github.com/yao-pkg/pkg) and creates platform-specific installers that:
|
||||||
|
|
||||||
|
1. Install the `safe-chain` binary to the system PATH
|
||||||
|
2. Generate and install the CA certificate in the OS trust store
|
||||||
|
3. Configure the system for automatic MITM proxy interception
|
||||||
|
|
@ -65,6 +65,11 @@ async function bundleWithEsbuild() {
|
||||||
loader: {
|
loader: {
|
||||||
'.json': 'json', // Inline JSON files
|
'.json': 'json', // Inline JSON files
|
||||||
},
|
},
|
||||||
|
// We need to inject a polyfill because:
|
||||||
|
// source code is ESM (uses import.meta.url)
|
||||||
|
// target is CommonJS (required by pkg)
|
||||||
|
// CommonJS doesn't have import.meta.url, and ESM doesn't have __filename/__dirname
|
||||||
|
// create a fake import.meta.url from __filename
|
||||||
banner: {
|
banner: {
|
||||||
js: `// Polyfill for import.meta.url in CommonJS
|
js: `// Polyfill for import.meta.url in CommonJS
|
||||||
var __filename = __filename || (() => {
|
var __filename = __filename || (() => {
|
||||||
|
|
@ -85,13 +90,10 @@ var import_meta_url = typeof __filename !== 'undefined' ? require('url').pathToF
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✓ Bundle created at:', outputFile);
|
console.log('Bundle created at:', outputFile);
|
||||||
return outputFile;
|
return outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build macOS binary and installer
|
|
||||||
*/
|
|
||||||
async function buildMacOS() {
|
async function buildMacOS() {
|
||||||
console.log('Building macOS binary...');
|
console.log('Building macOS binary...');
|
||||||
|
|
||||||
|
|
@ -196,11 +198,26 @@ async function createMacOSInstaller(binaryPath) {
|
||||||
writeFileSync(join(scriptsDir, 'postinstall'), postinstallScript, { mode: 0o755 });
|
writeFileSync(join(scriptsDir, 'postinstall'), postinstallScript, { mode: 0o755 });
|
||||||
writeFileSync(join(installerDir, 'uninstall.sh'), uninstallScript, { mode: 0o755 });
|
writeFileSync(join(installerDir, 'uninstall.sh'), uninstallScript, { mode: 0o755 });
|
||||||
|
|
||||||
console.log('✓ macOS installer package created at:', installerDir);
|
// Run pkgbuild to create the .pkg file
|
||||||
console.log('');
|
console.log('Running pkgbuild...');
|
||||||
console.log('To create a .pkg installer, run:');
|
const pkgName = 'SafeChain.pkg';
|
||||||
console.log(` cd ${installerDir}`);
|
const pkgPath = join(installerDir, pkgName);
|
||||||
console.log(' pkgbuild --root resources --scripts scripts --identifier com.aikido.safe-chain --version 1.0.0 --install-location /tmp/safe-chain-install SafeChain.pkg');
|
|
||||||
|
// Get version from package.json
|
||||||
|
const packageJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
|
||||||
|
const version = packageJson.version;
|
||||||
|
|
||||||
|
const pkgbuildArgs = [
|
||||||
|
'--root', resourcesDir,
|
||||||
|
'--scripts', scriptsDir,
|
||||||
|
'--identifier', 'com.aikido.safe-chain',
|
||||||
|
'--version', version,
|
||||||
|
'--install-location', '/tmp/safe-chain-install',
|
||||||
|
pkgPath
|
||||||
|
];
|
||||||
|
|
||||||
|
await execAsync(`pkgbuild ${pkgbuildArgs.join(' ')}`);
|
||||||
|
console.log(`Mac OS Installer created at: ${pkgPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { join } from 'node:path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate certificate files (simple version for installer build)
|
* Generate certificate files (simple version for installer build)
|
||||||
* For the full CLI version with nice output, use: safe-chain generate-cert
|
* For the full CLI version with nice output, use: safe-chain _generate-cert
|
||||||
*
|
*
|
||||||
* @param {string} outputDir - Directory to save certificate files
|
* @param {string} outputDir - Directory to save certificate files
|
||||||
* @returns {Promise<{certPath: string, keyPath: string}>}
|
* @returns {Promise<{certPath: string, keyPath: string}>}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@aikidosec/safe-chain-installer",
|
"name": "@aikidosec/safe-chain-installer",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Installer for Aikido Safe Chain - creates standalone binaries and installs system certificates",
|
"description": "Installer for Aikido Safe Chain - creates standalone binaries and contains pre- and post install functionality",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
21
installer/scripts/darwin_build_installer.sh
Executable file
21
installer/scripts/darwin_build_installer.sh
Executable file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Get the directory where this script is located
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
INSTALLER_ROOT="${SCRIPT_DIR}/.."
|
||||||
|
|
||||||
|
echo "=== Building Safe Chain Installer for macOS ==="
|
||||||
|
|
||||||
|
# Ensure we are in the installer directory
|
||||||
|
cd "${INSTALLER_ROOT}"
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
echo "Installing build dependencies..."
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Build the binary and installer using the Node.js build script
|
||||||
|
echo "Building binary and installer..."
|
||||||
|
node build.js --platform=macos
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
|
|
@ -12,32 +12,32 @@ mkdir -p /usr/local/bin
|
||||||
cp "${INSTALL_LOCATION}/safe-chain" /usr/local/bin/safe-chain
|
cp "${INSTALL_LOCATION}/safe-chain" /usr/local/bin/safe-chain
|
||||||
chmod +x /usr/local/bin/safe-chain
|
chmod +x /usr/local/bin/safe-chain
|
||||||
|
|
||||||
# Setup certificate directory in user's home
|
# Setup system-wide certificate directory
|
||||||
# The proxy will use ~/.safe-chain/certs/ so we need to ensure it exists
|
# We use a shared location so we don't need to worry about which user is running the agent
|
||||||
# and install the certificate from there
|
CERT_DIR="/usr/local/share/safe-chain/certs"
|
||||||
# Get the actual user (not root) who invoked the installer
|
|
||||||
# When using 'installer' command, SUDO_USER is not set, so we use the console user
|
|
||||||
ACTUAL_USER=$(stat -f '%Su' /dev/console)
|
|
||||||
|
|
||||||
# Get the home directory of the actual user
|
|
||||||
USER_HOME=$(eval echo "~${ACTUAL_USER}")
|
|
||||||
|
|
||||||
CERT_DIR="${USER_HOME}/.safe-chain/certs"
|
|
||||||
mkdir -p "${CERT_DIR}"
|
mkdir -p "${CERT_DIR}"
|
||||||
# Set ownership immediately after creating directory
|
|
||||||
chown -R "${ACTUAL_USER}:staff" "${USER_HOME}/.safe-chain"
|
|
||||||
|
|
||||||
# Generate certificate if it doesn't exist
|
# Generate certificate if it doesn't exist
|
||||||
# This ensures the same cert is used by both the proxy and system trust store
|
|
||||||
if [ ! -f "${CERT_DIR}/ca-cert.pem" ]; then
|
if [ ! -f "${CERT_DIR}/ca-cert.pem" ]; then
|
||||||
echo "Generating Safe Chain CA certificate..."
|
echo "Generating Safe Chain CA certificate..."
|
||||||
# Run as the actual user with their HOME set, not root
|
# Run as root (installer context) - no need to switch users
|
||||||
sudo -u "${ACTUAL_USER}" HOME="${USER_HOME}" /usr/local/bin/safe-chain generate-cert --output "${CERT_DIR}"
|
/usr/local/bin/safe-chain _generate-cert --output "${CERT_DIR}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set correct ownership (important since installer runs as root)
|
# Set permissions so any user can read the certs (required for the agent to load them)
|
||||||
# Do this AFTER generating certificates so they get the right ownership too
|
# Directory is executable/readable by all
|
||||||
chown -R "${ACTUAL_USER}:staff" "${USER_HOME}/.safe-chain"
|
chmod 755 "/usr/local/share/safe-chain"
|
||||||
|
chmod 755 "${CERT_DIR}"
|
||||||
|
|
||||||
|
# PUBLIC Certificate: Readable by everyone (644)
|
||||||
|
chmod 644 "${CERT_DIR}/ca-cert.pem"
|
||||||
|
|
||||||
|
# PRIVATE Key: Readable ONLY by the owner (600)
|
||||||
|
# This is critical for security.
|
||||||
|
chmod 600 "${CERT_DIR}/ca-key.pem"
|
||||||
|
|
||||||
|
# Ensure the actual user owns the files so the agent (running as user) can read them
|
||||||
|
chown -R "${ACTUAL_USER}:staff" "/usr/local/share/safe-chain"
|
||||||
|
|
||||||
# Install certificate in system trust store
|
# Install certificate in system trust store
|
||||||
echo "Installing Safe Chain CA certificate in system trust store..."
|
echo "Installing Safe Chain CA certificate in system trust store..."
|
||||||
|
|
@ -52,9 +52,15 @@ fi
|
||||||
# Start safe-chain as a background service
|
# Start safe-chain as a background service
|
||||||
echo "Starting Safe Chain proxy service..."
|
echo "Starting Safe Chain proxy service..."
|
||||||
|
|
||||||
|
# Get the actual user to install the LaunchAgent in their home
|
||||||
|
# (We still need this for the LaunchAgent, but not for file permissions)
|
||||||
|
ACTUAL_USER=$(stat -f '%Su' /dev/console)
|
||||||
|
USER_HOME=$(eval echo "~${ACTUAL_USER}")
|
||||||
|
|
||||||
# Create LaunchAgent for auto-start on login
|
# Create LaunchAgent for auto-start on login
|
||||||
LAUNCH_AGENT_DIR="${USER_HOME}/Library/LaunchAgents"
|
LAUNCH_AGENT_DIR="${USER_HOME}/Library/LaunchAgents"
|
||||||
mkdir -p "${LAUNCH_AGENT_DIR}"
|
mkdir -p "${LAUNCH_AGENT_DIR}"
|
||||||
|
chown "${ACTUAL_USER}:staff" "${LAUNCH_AGENT_DIR}"
|
||||||
|
|
||||||
PLIST_PATH="${LAUNCH_AGENT_DIR}/com.aikido.safe-chain.plist"
|
PLIST_PATH="${LAUNCH_AGENT_DIR}/com.aikido.safe-chain.plist"
|
||||||
cat > "${PLIST_PATH}" << EOF
|
cat > "${PLIST_PATH}" << EOF
|
||||||
|
|
@ -77,6 +83,8 @@ cat > "${PLIST_PATH}" << EOF
|
||||||
<string>http://localhost:8080</string>
|
<string>http://localhost:8080</string>
|
||||||
<key>NODE_EXTRA_CA_CERTS</key>
|
<key>NODE_EXTRA_CA_CERTS</key>
|
||||||
<string>${CERT_DIR}/ca-cert.pem</string>
|
<string>${CERT_DIR}/ca-cert.pem</string>
|
||||||
|
<key>SAFE_CHAIN_CERT_DIR</key>
|
||||||
|
<string>${CERT_DIR}</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|
@ -93,9 +101,6 @@ EOF
|
||||||
# Set correct ownership for plist
|
# Set correct ownership for plist
|
||||||
chown "${ACTUAL_USER}:staff" "${PLIST_PATH}"
|
chown "${ACTUAL_USER}:staff" "${PLIST_PATH}"
|
||||||
|
|
||||||
# Set correct ownership for plist
|
|
||||||
chown "${ACTUAL_USER}:staff" "${PLIST_PATH}"
|
|
||||||
|
|
||||||
# Load the LaunchAgent to start the service now
|
# Load the LaunchAgent to start the service now
|
||||||
# Need to run as the actual user, not root
|
# Need to run as the actual user, not root
|
||||||
sudo -u "${ACTUAL_USER}" launchctl load "${PLIST_PATH}" 2>/dev/null || true
|
sudo -u "${ACTUAL_USER}" launchctl load "${PLIST_PATH}" 2>/dev/null || true
|
||||||
|
|
|
||||||
|
|
@ -3,36 +3,54 @@ set -e
|
||||||
|
|
||||||
echo "Uninstalling Safe Chain..."
|
echo "Uninstalling Safe Chain..."
|
||||||
|
|
||||||
USER_HOME="${HOME}"
|
# Get the actual user
|
||||||
if [ -z "${USER_HOME}" ]; then
|
if [ -n "${SUDO_USER}" ]; then
|
||||||
USER_HOME=~
|
ACTUAL_USER="${SUDO_USER}"
|
||||||
|
else
|
||||||
|
ACTUAL_USER=$(stat -f '%Su' /dev/console)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Get the home directory of the actual user
|
||||||
|
USER_HOME=$(eval echo "~${ACTUAL_USER}")
|
||||||
|
|
||||||
|
echo "Detected user: ${ACTUAL_USER}"
|
||||||
|
echo "User home: ${USER_HOME}"
|
||||||
|
|
||||||
# Stop and unload the LaunchAgent
|
# Stop and unload the LaunchAgent
|
||||||
PLIST_PATH="${USER_HOME}/Library/LaunchAgents/com.aikido.safe-chain.plist"
|
PLIST_PATH="${USER_HOME}/Library/LaunchAgents/com.aikido.safe-chain.plist"
|
||||||
|
SERVICE_LABEL="com.aikido.safe-chain"
|
||||||
|
|
||||||
|
echo "Stopping Safe Chain service..."
|
||||||
if [ -f "${PLIST_PATH}" ]; then
|
if [ -f "${PLIST_PATH}" ]; then
|
||||||
echo "Stopping Safe Chain service..."
|
# Run launchctl as the user
|
||||||
launchctl unload "${PLIST_PATH}" 2>/dev/null || true
|
sudo -u "${ACTUAL_USER}" launchctl unload "${PLIST_PATH}" 2>/dev/null || true
|
||||||
rm -f "${PLIST_PATH}"
|
rm -f "${PLIST_PATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Ensure service is removed even if plist is gone
|
||||||
|
sudo -u "${ACTUAL_USER}" launchctl remove "${SERVICE_LABEL}" 2>/dev/null || true
|
||||||
|
|
||||||
# Remove system-wide environment variables
|
# Remove system-wide environment variables
|
||||||
echo "Removing proxy environment variables..."
|
echo "Removing proxy environment variables..."
|
||||||
launchctl unsetenv HTTPS_PROXY 2>/dev/null || true
|
# Run launchctl as the user
|
||||||
launchctl unsetenv GLOBAL_AGENT_HTTP_PROXY 2>/dev/null || true
|
sudo -u "${ACTUAL_USER}" launchctl unsetenv HTTPS_PROXY 2>/dev/null || true
|
||||||
launchctl unsetenv NODE_EXTRA_CA_CERTS 2>/dev/null || true
|
sudo -u "${ACTUAL_USER}" launchctl unsetenv GLOBAL_AGENT_HTTP_PROXY 2>/dev/null || true
|
||||||
|
sudo -u "${ACTUAL_USER}" launchctl unsetenv NODE_EXTRA_CA_CERTS 2>/dev/null || true
|
||||||
|
|
||||||
# Remove binary
|
# Remove binary
|
||||||
rm -f /usr/local/bin/safe-chain
|
rm -f /usr/local/bin/safe-chain
|
||||||
|
|
||||||
# Remove certificate from system keychain
|
# Remove certificate from system keychain
|
||||||
CERT_PATH="${USER_HOME}/.safe-chain/certs/ca-cert.pem"
|
CERT_DIR="/usr/local/share/safe-chain/certs"
|
||||||
if [ -f "${CERT_PATH}" ]; then
|
if [ -f "${CERT_DIR}/ca-cert.pem" ]; then
|
||||||
echo "Removing certificate from system trust store..."
|
echo "Removing certificate from system trust store..."
|
||||||
# Find and delete the certificate by common name
|
# Find and delete the certificate by common name
|
||||||
security delete-certificate -c "safe-chain proxy" /Library/Keychains/System.keychain 2>/dev/null || true
|
security delete-certificate -c "safe-chain proxy" /Library/Keychains/System.keychain 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Remove system-wide configuration and certificates
|
||||||
|
rm -rf /usr/local/share/safe-chain
|
||||||
|
|
||||||
# Optionally remove the .safe-chain directory (commented out to preserve user data)
|
# Optionally remove the .safe-chain directory (commented out to preserve user data)
|
||||||
# echo "Remove ~/.safe-chain directory? (y/N)"
|
# echo "Remove ~/.safe-chain directory? (y/N)"
|
||||||
# read -r response
|
# read -r response
|
||||||
|
|
|
||||||
5
installer/scripts/linux_build_installer.sh
Executable file
5
installer/scripts/linux_build_installer.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
echo "=== Building Safe Chain Installer for Linux ==="
|
||||||
|
echo "TODO: Implement Linux installer build"
|
||||||
|
echo "This is a placeholder script."
|
||||||
|
exit 0
|
||||||
5
installer/scripts/windows_build_installer.bat
Normal file
5
installer/scripts/windows_build_installer.bat
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
@echo off
|
||||||
|
echo === Building Safe Chain Installer for Windows ===
|
||||||
|
echo TODO: Implement Windows installer build
|
||||||
|
echo This is a placeholder script.
|
||||||
|
exit /b 0
|
||||||
|
|
@ -36,7 +36,8 @@ if (command === "setup") {
|
||||||
// Pass remaining arguments to runCommand
|
// Pass remaining arguments to runCommand
|
||||||
const runArgs = process.argv.slice(3);
|
const runArgs = process.argv.slice(3);
|
||||||
runCommand(runArgs);
|
runCommand(runArgs);
|
||||||
} else if (command === "generate-cert") {
|
} else if (command === "_generate-cert") {
|
||||||
|
// Internal command for installer
|
||||||
// Pass remaining arguments to generateCertCommand
|
// Pass remaining arguments to generateCertCommand
|
||||||
const certArgs = process.argv.slice(3);
|
const certArgs = process.argv.slice(3);
|
||||||
generateCertCommand(certArgs);
|
generateCertCommand(certArgs);
|
||||||
|
|
@ -59,9 +60,7 @@ function writeHelp() {
|
||||||
ui.writeInformation(
|
ui.writeInformation(
|
||||||
`Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
|
`Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
|
||||||
"teardown"
|
"teardown"
|
||||||
)}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("run")}, ${chalk.cyan(
|
)}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("run")}, ${chalk.cyan("help")}, ${chalk.cyan("--version")}`
|
||||||
"generate-cert"
|
|
||||||
)}, ${chalk.cyan("help")}, ${chalk.cyan("--version")}`
|
|
||||||
);
|
);
|
||||||
ui.emptyLine();
|
ui.emptyLine();
|
||||||
ui.writeInformation(
|
ui.writeInformation(
|
||||||
|
|
@ -84,11 +83,6 @@ function writeHelp() {
|
||||||
"safe-chain run"
|
"safe-chain run"
|
||||||
)}: Run the proxy as a standalone service. Sets system-wide proxy environment variables. Options: --verbose`
|
)}: Run the proxy as a standalone service. Sets system-wide proxy environment variables. Options: --verbose`
|
||||||
);
|
);
|
||||||
ui.writeInformation(
|
|
||||||
`- ${chalk.cyan(
|
|
||||||
"safe-chain generate-cert"
|
|
||||||
)}: Generate CA certificate for MITM proxy. Options: --output <directory>`
|
|
||||||
);
|
|
||||||
ui.writeInformation(
|
ui.writeInformation(
|
||||||
`- ${chalk.cyan(
|
`- ${chalk.cyan(
|
||||||
"safe-chain --version"
|
"safe-chain --version"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
|
|
||||||
const certFolder = path.join(os.homedir(), ".safe-chain", "certs");
|
const certFolder = process.env.SAFE_CHAIN_CERT_DIR || path.join(os.homedir(), ".safe-chain", "certs");
|
||||||
const ca = loadCa();
|
const ca = loadCa();
|
||||||
|
|
||||||
const certCache = new Map();
|
const certCache = new Map();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue