release r007 security hardening and redesigned UI
This commit is contained in:
+63
-22
@@ -5,7 +5,7 @@
|
||||
* Config-Datei: /boot/config/plugins/mua/mua.conf (chmod 600)
|
||||
* Format (INI):
|
||||
* MUA_API_KEY=<64 hex chars>
|
||||
* MUA_ENABLED_TOOLS=all | <comma-separated tool names>
|
||||
* MUA_ENABLED_TOOLS=all | none | <comma-separated tool names>
|
||||
*
|
||||
* Auth-Flow:
|
||||
* Client sendet: Authorization: Bearer ***
|
||||
@@ -27,16 +27,39 @@ const CONFIG_DIR = process.env["MUA_CONFIG_DIR"] ?? "/boot/config/plugins/mua";
|
||||
const CONFIG_FILE = `${CONFIG_DIR}/mua.conf`;
|
||||
|
||||
// ── Config-Struktur ─────────────────────────────────────────────────────
|
||||
interface MUAConfig {
|
||||
export interface MUAConfig {
|
||||
apiKey: string;
|
||||
enabledTools: string[]; // leer = alle aktiv
|
||||
enabledTools: string[];
|
||||
allToolsEnabled: boolean;
|
||||
}
|
||||
|
||||
// Sichere Grundeinstellung für Neuinstallationen. Schreibzugriffe, aktive
|
||||
// Netzwerkscans und die Root-Shell müssen ausdrücklich in der GUI aktiviert
|
||||
// werden.
|
||||
export const SAFE_DEFAULT_TOOLS = [
|
||||
"unraid_docker_list",
|
||||
"unraid_docker_inspect",
|
||||
"unraid_docker_logs",
|
||||
"unraid_docker_analyze_logs",
|
||||
"unraid_docker_processes",
|
||||
"unraid_docker_stats",
|
||||
"unraid_docker_info",
|
||||
"unraid_network_inventory",
|
||||
"unraid_network_list",
|
||||
"unraid_network_inspect",
|
||||
"unraid_network_host_state",
|
||||
"unraid_system_connection_test",
|
||||
];
|
||||
|
||||
// ── Config laden ────────────────────────────────────────────────────────
|
||||
let cachedConfig: MUAConfig | null = null;
|
||||
|
||||
function parseConfig(content: string): MUAConfig {
|
||||
const cfg: MUAConfig = { apiKey: "", enabledTools: [] };
|
||||
export function parseConfig(content: string): MUAConfig {
|
||||
const cfg: MUAConfig = {
|
||||
apiKey: "",
|
||||
enabledTools: [...SAFE_DEFAULT_TOOLS],
|
||||
allToolsEnabled: false,
|
||||
};
|
||||
for (const line of content.split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith("#") || trimmed === "") continue;
|
||||
@@ -47,13 +70,18 @@ function parseConfig(content: string): MUAConfig {
|
||||
if (key === "MUA_API_KEY") {
|
||||
cfg.apiKey = value;
|
||||
} else if (key === "MUA_ENABLED_TOOLS") {
|
||||
if (value === "all" || value === "") {
|
||||
if (value === "all") {
|
||||
cfg.enabledTools = [];
|
||||
cfg.allToolsEnabled = true;
|
||||
} else if (value === "none" || value === "") {
|
||||
cfg.enabledTools = [];
|
||||
cfg.allToolsEnabled = false;
|
||||
} else {
|
||||
cfg.enabledTools = value
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
cfg.allToolsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +94,10 @@ function loadConfig(): MUAConfig {
|
||||
// 1. Env-Override (für Testing / Docker)
|
||||
const envToken = process.env["MUA_API_KEY"];
|
||||
if (envToken && envToken.length > 0) {
|
||||
cachedConfig = { apiKey: envToken, enabledTools: [] };
|
||||
const envTools = process.env["MUA_ENABLED_TOOLS"] ?? "";
|
||||
cachedConfig = parseConfig(
|
||||
`MUA_API_KEY=${envToken}\nMUA_ENABLED_TOOLS=${envTools || "none"}\n`,
|
||||
);
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
@@ -84,7 +115,11 @@ function loadConfig(): MUAConfig {
|
||||
|
||||
// 3. Neue Config generieren
|
||||
const newToken = randomBytes(32).toString("hex"); // 64 hex chars
|
||||
cachedConfig = { apiKey: newToken, enabledTools: [] };
|
||||
cachedConfig = {
|
||||
apiKey: newToken,
|
||||
enabledTools: [...SAFE_DEFAULT_TOOLS],
|
||||
allToolsEnabled: false,
|
||||
};
|
||||
|
||||
try {
|
||||
mkdirSync(CONFIG_DIR, { recursive: true });
|
||||
@@ -92,7 +127,7 @@ function loadConfig(): MUAConfig {
|
||||
console.log(`[MUA] Neue Config generiert: ${CONFIG_FILE}`);
|
||||
} catch (e) {
|
||||
console.error(`[MUA] WARNUNG: Config konnte nicht gespeichert werden: ${String(e)}`);
|
||||
console.error(`[MUA] API-Key (nur in diesem Log): ${newToken}`);
|
||||
console.error("[MUA] Aus Sicherheitsgründen wird der flüchtige API-Key nicht protokolliert.");
|
||||
}
|
||||
|
||||
return cachedConfig;
|
||||
@@ -100,17 +135,18 @@ function loadConfig(): MUAConfig {
|
||||
|
||||
// ── Config schreiben ────────────────────────────────────────────────────
|
||||
function writeConfigFile(cfg: MUAConfig): void {
|
||||
const toolsLine =
|
||||
cfg.enabledTools.length === 0
|
||||
? "MUA_ENABLED_TOOLS=all"
|
||||
: `MUA_ENABLED_TOOLS=${cfg.enabledTools.join(",")}`;
|
||||
const toolsValue = cfg.allToolsEnabled
|
||||
? "all"
|
||||
: cfg.enabledTools.length === 0
|
||||
? "none"
|
||||
: cfg.enabledTools.join(",");
|
||||
const content = [
|
||||
"# MUA Configuration",
|
||||
"# API-Key für MCP-HTTP-Endpunkt (Port 3002)",
|
||||
"# Format: Authorization: Bearer ***",
|
||||
`MUA_API_KEY=${cfg.apiKey}`,
|
||||
"# Aktive Tools (all = alle, oder kommagetrennte Tool-Namen)",
|
||||
toolsLine,
|
||||
"# Aktive Tools (all = alle, none = keine, oder kommagetrennte Tool-Namen)",
|
||||
`MUA_ENABLED_TOOLS=${toolsValue}`,
|
||||
"",
|
||||
].join("\n");
|
||||
writeFileSync(CONFIG_FILE, content, { mode: 0o600 });
|
||||
@@ -148,13 +184,18 @@ export function getEnabledTools(): string[] {
|
||||
return loadConfig().enabledTools;
|
||||
}
|
||||
|
||||
export function getAllToolsEnabled(): boolean {
|
||||
return loadConfig().allToolsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die aktiven Tools.
|
||||
* Leer-Array = alle Tools aktiv.
|
||||
* Ein leeres Array bedeutet ausdrücklich: keine Tools aktiv.
|
||||
*/
|
||||
export function setEnabledTools(names: string[]): void {
|
||||
export function setEnabledTools(names: string[], allToolsEnabled = false): void {
|
||||
const cfg = loadConfig();
|
||||
cfg.enabledTools = names;
|
||||
cfg.enabledTools = [...new Set(names)];
|
||||
cfg.allToolsEnabled = allToolsEnabled;
|
||||
saveConfig(cfg);
|
||||
}
|
||||
|
||||
@@ -164,7 +205,7 @@ export function setEnabledTools(names: string[]): void {
|
||||
*/
|
||||
export function isToolEnabled(toolName: string): boolean {
|
||||
const enabled = getEnabledTools();
|
||||
if (enabled.length === 0) return true; // alle aktiv
|
||||
if (getAllToolsEnabled()) return true;
|
||||
return enabled.includes(toolName);
|
||||
}
|
||||
|
||||
@@ -192,7 +233,7 @@ export function checkAuth(req: Request): boolean {
|
||||
return diff === 0;
|
||||
}
|
||||
|
||||
// ── Token für WebGUI-Tab (nur lesbar) ───────────────────────────────────
|
||||
export function getTokenForDisplay(): string {
|
||||
return getApiKey();
|
||||
export function getMaskedApiKey(): string {
|
||||
const key = getApiKey();
|
||||
return key.length >= 8 ? `••••••••${key.slice(-8)}` : "gesetzt";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user