mirror of
https://github.com/myronblair/jarvis
synced 2026-07-27 16:22:55 -05:00
Rotate agent registration key; remove key literals from public scripts/UI
- install.sh, install-agent.sh: require JARVIS_REG_KEY env var or interactive prompt instead of baked-in key (matches install-mac.sh/install-windows.ps1 behavior) - netscan.php: reuse AGENT_REGISTRATION_KEY constant instead of a duplicate literal - agent.php + api.php: add session-authed "regkey" action so the admin install modal fetches the current key at runtime - jarvis-agents.js: fetch reg key via /api/agent/regkey instead of hardcoding it; pass JARVIS_REG_KEY in the Linux install one-liner - INFRASTRUCTURE-REFERENCE.md: scrub old key literal (rotated; real value lives only in api/config.php on VM211) Key rotated on the box + rolled out to all 11 agents (verified all re-register online). New value is in the gitignored api/config.php only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,7 +54,7 @@ function update_agent_seen(string $agentId, string $status = 'online', ?string $
|
|||||||
// ── Auth (all actions except register) ───────────────────────────────────────
|
// ── Auth (all actions except register) ───────────────────────────────────────
|
||||||
|
|
||||||
$agentKey = $_SERVER['HTTP_X_AGENT_KEY'] ?? '';
|
$agentKey = $_SERVER['HTTP_X_AGENT_KEY'] ?? '';
|
||||||
$browserActions = ['list', 'status', 'myip'];
|
$browserActions = ['list', 'status', 'myip', 'regkey'];
|
||||||
|
|
||||||
if ($agentAction !== 'register') {
|
if ($agentAction !== 'register') {
|
||||||
if (in_array($agentAction, $browserActions)) {
|
if (in_array($agentAction, $browserActions)) {
|
||||||
@@ -212,6 +212,10 @@ switch ($agentAction) {
|
|||||||
);
|
);
|
||||||
agent_ok();
|
agent_ok();
|
||||||
|
|
||||||
|
// ── REGKEY (browser: session-authed fetch of registration key) ───────────
|
||||||
|
case 'regkey':
|
||||||
|
agent_ok(['registration_key' => AGENT_REGISTRATION_KEY]);
|
||||||
|
|
||||||
// ── LIST (admin: get all agents status) ──────────────────────────────────
|
// ── LIST (admin: get all agents status) ──────────────────────────────────
|
||||||
case 'list':
|
case 'list':
|
||||||
// Mark agents offline if last_seen > 2 minutes ago
|
// Mark agents offline if last_seen > 2 minutes ago
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Network scan push endpoint — called by PVE1 cron with nmap results
|
// Network scan push endpoint — called by PVE1 cron with nmap results
|
||||||
// Authenticates via X-Registration-Key header (same key as agent installer)
|
// Authenticates via X-Registration-Key header (same key as agent installer)
|
||||||
|
|
||||||
define('NETSCAN_KEY', 'f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518');
|
define('NETSCAN_KEY', AGENT_REGISTRATION_KEY);
|
||||||
|
|
||||||
if ($method !== 'POST') {
|
if ($method !== 'POST') {
|
||||||
echo json_encode(['error' => 'POST only']); exit;
|
echo json_encode(['error' => 'POST only']); exit;
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ Webhook secret: `4c8805f0285214ff0a0602b5880270b935f36a896946c7f1`
|
|||||||
|
|
||||||
### Agent System
|
### Agent System
|
||||||
Agents installed on all servers — phone home every 10s (heartbeat) / 30s (metrics).
|
Agents installed on all servers — phone home every 10s (heartbeat) / 30s (metrics).
|
||||||
Registration key: `f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518`
|
Registration key: `[rotated 2026-07-07 — stored in api/config.php on VM211, not documented here]`
|
||||||
Install command: `curl -sk http://10.48.200.211/install-agent.sh | bash -s <hostname> <linux|proxmox>`
|
Install command: `curl -sk http://10.48.200.211/install-agent.sh | bash -s <hostname> <linux|proxmox>`
|
||||||
|
|
||||||
### Self-Healing Watchdog
|
### Self-Healing Watchdog
|
||||||
@@ -918,7 +918,7 @@ sshpass -p 'Joker1974!!!' ssh root@10.48.200.90 \
|
|||||||
| Service | Key |
|
| Service | Key |
|
||||||
|---------|-----|
|
|---------|-----|
|
||||||
| GitHub PAT | `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` (rotated 2026-07-05, scopes `repo`+`workflow`) |
|
| GitHub PAT | `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` (rotated 2026-07-05, scopes `repo`+`workflow`) |
|
||||||
| JARVIS Agent Registration | `f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518` |
|
| JARVIS Agent Registration | `[rotated 2026-07-07 — stored in api/config.php on VM211, not documented here]` |
|
||||||
| Proxmox API Token | `root@pam!jarvis=c45b5feb-f9a9-445d-a626-14fbb959f78b` |
|
| Proxmox API Token | `root@pam!jarvis=c45b5feb-f9a9-445d-a626-14fbb959f78b` |
|
||||||
| HA Long-lived Token | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzNmI0N2I1Njk5ZGQ0MTQ2ODMwZWFmYjZiYTQ1MjJkMSIsImlhdCI6MTc4MDIwMzU5NCwiZXhwIjoyMDk1NTYzNTk0fQ.sYRok-jRDlA4lFgWxLQELcEjkJNGQdprk6ZziLwLtXE` |
|
| HA Long-lived Token | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzNmI0N2I1Njk5ZGQ0MTQ2ODMwZWFmYjZiYTQ1MjJkMSIsImlhdCI6MTc4MDIwMzU5NCwiZXhwIjoyMDk1NTYzNTk0fQ.sYRok-jRDlA4lFgWxLQELcEjkJNGQdprk6ZziLwLtXE` |
|
||||||
| Sonarr API | `b43e04350a594846b4ee95261c29e9e0` |
|
| Sonarr API | `b43e04350a594846b4ee95261c29e9e0` |
|
||||||
|
|||||||
@@ -21,7 +21,14 @@ JARVIS_HOST=""
|
|||||||
INSTALL_DIR="/opt/jarvis-agent"
|
INSTALL_DIR="/opt/jarvis-agent"
|
||||||
CONFIG_DIR="/etc/jarvis-agent"
|
CONFIG_DIR="/etc/jarvis-agent"
|
||||||
STATE_DIR="/var/lib/jarvis-agent"
|
STATE_DIR="/var/lib/jarvis-agent"
|
||||||
REG_KEY="f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518"
|
REG_KEY="${JARVIS_REG_KEY:-}"
|
||||||
|
if [ -z "$REG_KEY" ] && [ -r /dev/tty ]; then
|
||||||
|
read -rp "Enter JARVIS registration key: " REG_KEY </dev/tty
|
||||||
|
fi
|
||||||
|
if [ -z "$REG_KEY" ]; then
|
||||||
|
echo "ERROR: registration key required (set JARVIS_REG_KEY env var or enter at prompt)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
SERVICE_FILE="/etc/systemd/system/jarvis-agent.service"
|
SERVICE_FILE="/etc/systemd/system/jarvis-agent.service"
|
||||||
|
|
||||||
echo "=== JARVIS Agent Installer v3.0 ==="
|
echo "=== JARVIS Agent Installer v3.0 ==="
|
||||||
|
|||||||
+128
-128
@@ -1,128 +1,128 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* JARVIS API Router — fault-isolated per endpoint
|
* JARVIS API Router — fault-isolated per endpoint
|
||||||
* A ParseError or fatal in any endpoint file returns JSON 500 for that
|
* A ParseError or fatal in any endpoint file returns JSON 500 for that
|
||||||
* endpoint only; all other endpoints continue to work normally.
|
* endpoint only; all other endpoints continue to work normally.
|
||||||
*/
|
*/
|
||||||
require_once __DIR__ . '/../api/config.php';
|
require_once __DIR__ . '/../api/config.php';
|
||||||
require_once __DIR__ . '/../api/lib/db.php';
|
require_once __DIR__ . '/../api/lib/db.php';
|
||||||
require_once __DIR__ . '/../api/lib/kb_engine.php';
|
require_once __DIR__ . '/../api/lib/kb_engine.php';
|
||||||
|
|
||||||
// Skip session for machine-agent calls and netscan/ping — each heartbeat would
|
// Skip session for machine-agent calls and netscan/ping — each heartbeat would
|
||||||
// otherwise create an empty session file, producing millions of files that slow
|
// otherwise create an empty session file, producing millions of files that slow
|
||||||
// session GC for all requests. Browser-facing agent sub-actions (list/status/myip)
|
// session GC for all requests. Browser-facing agent sub-actions (list/status/myip)
|
||||||
// still need a session to verify auth, so we only skip for machine-agent actions.
|
// still need a session to verify auth, so we only skip for machine-agent actions.
|
||||||
$_earlyParts = explode('/', trim(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/'));
|
$_earlyParts = explode('/', trim(parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/'));
|
||||||
if (($_earlyParts[0] ?? '') === 'api') array_shift($_earlyParts);
|
if (($_earlyParts[0] ?? '') === 'api') array_shift($_earlyParts);
|
||||||
$_e0 = $_earlyParts[0] ?? '';
|
$_e0 = $_earlyParts[0] ?? '';
|
||||||
$_e1 = $_earlyParts[1] ?? '';
|
$_e1 = $_earlyParts[1] ?? '';
|
||||||
$_skipSession = match(true) {
|
$_skipSession = match(true) {
|
||||||
$_e0 === 'ping' => true,
|
$_e0 === 'ping' => true,
|
||||||
$_e0 === 'netscan' => true,
|
$_e0 === 'netscan' => true,
|
||||||
$_e0 === 'agent' && !in_array($_e1, ['list','status','myip'], true) => true,
|
$_e0 === 'agent' && !in_array($_e1, ['list','status','myip','regkey'], true) => true,
|
||||||
default => false,
|
default => false,
|
||||||
};
|
};
|
||||||
if (!$_skipSession) {
|
if (!$_skipSession) {
|
||||||
session_start();
|
session_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
$_allowedOrigins = ['https://jarvis.orbishosting.com', 'http://jarvis.orbishosting.com'];
|
$_allowedOrigins = ['https://jarvis.orbishosting.com', 'http://jarvis.orbishosting.com'];
|
||||||
$_origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
$_origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||||
if (in_array($_origin, $_allowedOrigins, true)) {
|
if (in_array($_origin, $_allowedOrigins, true)) {
|
||||||
header('Access-Control-Allow-Origin: ' . $_origin);
|
header('Access-Control-Allow-Origin: ' . $_origin);
|
||||||
header('Access-Control-Allow-Credentials: true');
|
header('Access-Control-Allow-Credentials: true');
|
||||||
}
|
}
|
||||||
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||||
header('Access-Control-Allow-Headers: Content-Type, X-Session-Token');
|
header('Access-Control-Allow-Headers: Content-Type, X-Session-Token');
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
||||||
|
|
||||||
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||||
$method = $_SERVER['REQUEST_METHOD'];
|
$method = $_SERVER['REQUEST_METHOD'];
|
||||||
$path = trim(parse_url($uri, PHP_URL_PATH), '/');
|
$path = trim(parse_url($uri, PHP_URL_PATH), '/');
|
||||||
$parts = explode('/', $path);
|
$parts = explode('/', $path);
|
||||||
|
|
||||||
if (($parts[0] ?? '') === 'api') array_shift($parts);
|
if (($parts[0] ?? '') === 'api') array_shift($parts);
|
||||||
$endpoint = $parts[0] ?? '';
|
$endpoint = $parts[0] ?? '';
|
||||||
$action = $parts[1] ?? '';
|
$action = $parts[1] ?? '';
|
||||||
|
|
||||||
// ── Auth check (skip for auth / agent / netscan) ──────────────────────
|
// ── Auth check (skip for auth / agent / netscan) ──────────────────────
|
||||||
if (!\in_array($endpoint, ['auth', 'agent', 'netscan'], true)) {
|
if (!\in_array($endpoint, ['auth', 'agent', 'netscan'], true)) {
|
||||||
$token = $_SESSION['jarvis_token'] ?? ($_SERVER['HTTP_X_SESSION_TOKEN'] ?? '');
|
$token = $_SESSION['jarvis_token'] ?? ($_SERVER['HTTP_X_SESSION_TOKEN'] ?? '');
|
||||||
$isValid = !empty($token) && $token === ($_SESSION['jarvis_token'] ?? '');
|
$isValid = !empty($token) && $token === ($_SESSION['jarvis_token'] ?? '');
|
||||||
if (!$isValid) {
|
if (!$isValid) {
|
||||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||||
$isLocal = \in_array($ip, ['127.0.0.1', '::1', JARVIS_IP], true);
|
$isLocal = \in_array($ip, ['127.0.0.1', '::1', JARVIS_IP], true);
|
||||||
if (!$isLocal && $endpoint !== 'ping') {
|
if (!$isLocal && $endpoint !== 'ping') {
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
echo json_encode(['error' => 'Unauthorized', 'code' => 401]);
|
echo json_encode(['error' => 'Unauthorized', 'code' => 401]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($endpoint !== 'auth') session_write_close();
|
if ($endpoint !== 'auth') session_write_close();
|
||||||
|
|
||||||
$body = file_get_contents('php://input');
|
$body = file_get_contents('php://input');
|
||||||
$data = json_decode($body, true) ?? [];
|
$data = json_decode($body, true) ?? [];
|
||||||
|
|
||||||
// ── Fast ping (no file dispatch needed) ──────────────────────────────
|
// ── Fast ping (no file dispatch needed) ──────────────────────────────
|
||||||
if ($endpoint === 'ping') {
|
if ($endpoint === 'ping') {
|
||||||
echo json_encode(['status' => 'online', 'time' => date('c'), 'codename' => JARVIS_CODENAME]);
|
echo json_encode(['status' => 'online', 'time' => date('c'), 'codename' => JARVIS_CODENAME]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Endpoint → file map ───────────────────────────────────────────────
|
// ── Endpoint → file map ───────────────────────────────────────────────
|
||||||
$endpoints = [
|
$endpoints = [
|
||||||
'auth' => 'auth.php',
|
'auth' => 'auth.php',
|
||||||
'chat' => 'chat.php',
|
'chat' => 'chat.php',
|
||||||
'system' => 'system.php',
|
'system' => 'system.php',
|
||||||
'netscan' => 'netscan.php',
|
'netscan' => 'netscan.php',
|
||||||
'network' => 'network.php',
|
'network' => 'network.php',
|
||||||
'proxmox' => 'proxmox.php',
|
'proxmox' => 'proxmox.php',
|
||||||
'ha' => 'ha.php',
|
'ha' => 'ha.php',
|
||||||
'tts' => 'tts.php',
|
'tts' => 'tts.php',
|
||||||
'email' => 'email.php',
|
'email' => 'email.php',
|
||||||
'do' => 'do_server.php',
|
'do' => 'do_server.php',
|
||||||
'alerts' => 'alerts.php',
|
'alerts' => 'alerts.php',
|
||||||
'facts' => 'facts_collector.php',
|
'facts' => 'facts_collector.php',
|
||||||
'weather' => 'weather.php',
|
'weather' => 'weather.php',
|
||||||
'news' => 'news.php',
|
'news' => 'news.php',
|
||||||
'sites' => 'sites.php',
|
'sites' => 'sites.php',
|
||||||
'agent' => 'agent.php',
|
'agent' => 'agent.php',
|
||||||
'planner' => 'planner.php',
|
'planner' => 'planner.php',
|
||||||
'jellyfin' => 'jellyfin.php',
|
'jellyfin' => 'jellyfin.php',
|
||||||
'history' => 'history.php',
|
'history' => 'history.php',
|
||||||
'metrics' => 'metrics.php',
|
'metrics' => 'metrics.php',
|
||||||
'suggestions' => 'suggestions.php',
|
'suggestions' => 'suggestions.php',
|
||||||
'arc' => 'arc.php',
|
'arc' => 'arc.php',
|
||||||
'directives' => 'directives.php',
|
'directives' => 'directives.php',
|
||||||
'memory' => 'memory.php',
|
'memory' => 'memory.php',
|
||||||
'calendar' => 'calendar_sync.php',
|
'calendar' => 'calendar_sync.php',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!isset($endpoints[$endpoint])) {
|
if (!isset($endpoints[$endpoint])) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo json_encode(['error' => 'Unknown endpoint: ' . $endpoint]);
|
echo json_encode(['error' => 'Unknown endpoint: ' . $endpoint]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = __DIR__ . '/../api/endpoints/' . $endpoints[$endpoint];
|
$file = __DIR__ . '/../api/endpoints/' . $endpoints[$endpoint];
|
||||||
|
|
||||||
// ── Fault-isolated dispatch ───────────────────────────────────────────
|
// ── Fault-isolated dispatch ───────────────────────────────────────────
|
||||||
// ob_start() buffers any partial output so a mid-execution fatal doesn't
|
// ob_start() buffers any partial output so a mid-execution fatal doesn't
|
||||||
// send a broken response. catch(Throwable) catches ParseError, TypeError,
|
// send a broken response. catch(Throwable) catches ParseError, TypeError,
|
||||||
// and all other Errors + Exceptions in PHP 7+.
|
// and all other Errors + Exceptions in PHP 7+.
|
||||||
ob_start();
|
ob_start();
|
||||||
try {
|
try {
|
||||||
require $file;
|
require $file;
|
||||||
ob_end_flush();
|
ob_end_flush();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
echo json_encode(['error' => 'Endpoint unavailable', 'endpoint' => $endpoint, 'code' => 500]);
|
echo json_encode(['error' => 'Endpoint unavailable', 'endpoint' => $endpoint, 'code' => 500]);
|
||||||
error_log(sprintf('JARVIS API [%s] %s: %s in %s:%d',
|
error_log(sprintf('JARVIS API [%s] %s: %s in %s:%d',
|
||||||
$endpoint, get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()
|
$endpoint, get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,14 @@ JARVIS_HOST=""
|
|||||||
INSTALL_DIR="/opt/jarvis-agent"
|
INSTALL_DIR="/opt/jarvis-agent"
|
||||||
CONFIG_DIR="/etc/jarvis-agent"
|
CONFIG_DIR="/etc/jarvis-agent"
|
||||||
STATE_DIR="/var/lib/jarvis-agent"
|
STATE_DIR="/var/lib/jarvis-agent"
|
||||||
REG_KEY="f846a9aaf7ce9a61742c63c87c4186052a71d2a580c65518"
|
REG_KEY="${JARVIS_REG_KEY:-}"
|
||||||
|
if [ -z "$REG_KEY" ] && [ -r /dev/tty ]; then
|
||||||
|
read -rp "Enter JARVIS registration key: " REG_KEY </dev/tty
|
||||||
|
fi
|
||||||
|
if [ -z "$REG_KEY" ]; then
|
||||||
|
echo "ERROR: registration key required (set JARVIS_REG_KEY env var or enter at prompt)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
SERVICE_FILE="/etc/systemd/system/jarvis-agent.service"
|
SERVICE_FILE="/etc/systemd/system/jarvis-agent.service"
|
||||||
|
|
||||||
echo "=== JARVIS Agent Installer v3.0 ==="
|
echo "=== JARVIS Agent Installer v3.0 ==="
|
||||||
|
|||||||
Reference in New Issue
Block a user