mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 01:27:11 +00:00
Auto-generate API key + runtime reload on 401
Fix Docker single-container auth gap by ensuring an API key exists and is discoverable by both the Go server and Node.js console. Changes: - betterdesk-server/main.go: loadAPIKey() now checks the server_config DB entry and, if absent, auto-generates a 32-byte hex API key, writes it to .api_key (with logging) and continues to sync to the DB. - docker/entrypoint.sh: generates/persists a 32-byte hex API key at container startup (uses openssl with /dev/urandom fallback) and writes API_KEY env to file if provided. - web-nodejs/services/betterdeskApi.js: adds fs import and an Axios 401 interceptor that reloads .api_key from disk once and retries the failed request to handle race conditions where the Go server generates the key after Node cached an empty value. - .github/copilot-instructions.md: documents the Docker API key auto-generation (Phase 16) and related fixes. - tasks/lessons.md and tasks/todo.md: add lightweight triage notes and actions. This resolves the issue where the Devices page returned empty results due to missing X-API-Key in the single-container Docker setup and improves resilience during first-run key generation.
This commit is contained in:
@@ -450,6 +450,12 @@ sudo apt-get install -y build-essential libsqlite3-dev pkg-config libssl-dev git
|
||||
89. [x] **Credentials persistence hardening**: Plaintext `.admin_credentials` persistence is now opt-in via `STORE_ADMIN_CREDENTIALS=true` (default secure behavior: do not persist credentials files).
|
||||
90. [x] **Dependency vulnerability fixes**: Updated Node override for `tar` in `web-nodejs/package.json`; `npm audit --omit=dev` now reports 0 vulnerabilities. Added Go toolchain hardening (`go.mod` toolchain + installer checks) to avoid vulnerable Go 1.26.0 stdlib.
|
||||
|
||||
#### Docker — API Key Auto-Generation (Phase 16) ✅ COMPLETED 2026-03-15
|
||||
91. [x] **Root cause (Issue #59)**: Docker single-container never created `.api_key` file. Dashboard used public `/api/server/stats` (showed correct count), Devices page used protected `/api/peers` (401 → empty list). Node.js sent empty `X-API-Key` header because file didn't exist in volume.
|
||||
92. [x] **Go server fix (`main.go`)**: `loadAPIKey()` now has 5-step lookup: (1) `API_KEY` env var, (2) `.api_key` in key dir, (3) `.api_key` in DB dir, (4) NEW: `server_config` table, (5) NEW: auto-generate 32-byte hex key → write to `.api_key` file + sync to DB.
|
||||
93. [x] **Docker entrypoint fix (`docker/entrypoint.sh`)**: Generates API key before supervisord starts if `.api_key` file missing. Uses `openssl rand -hex 32` with `/dev/urandom` fallback. Also persists `API_KEY` env var to file if provided.
|
||||
94. [x] **Node.js resilience (`betterdeskApi.js`)**: Axios 401 interceptor re-reads `.api_key` from disk once on auth failure. Handles race condition where Go server generates key after Node.js cached empty value at startup.
|
||||
|
||||
---
|
||||
|
||||
## 🔄 System Statusu v3.0
|
||||
@@ -617,6 +623,7 @@ Pełna dokumentacja budowania: [BUILD_GUIDE.md](../docs/BUILD_GUIDE.md)
|
||||
22. ~~**SELinux volume mount issues (Issue #31)**~~ ✅ ROZWIĄZANE - Added SELinux documentation to DOCKER_TROUBLESHOOTING.md with 4 solutions (named volumes, `:z` flag, chcon, setenforce) — Phase 12
|
||||
23. ~~**Docker single-container port 5000 conflict (Issue #56)**~~ ✅ ROZWIĄZANE - Go server `config.LoadEnv()` read generic `PORT=5000` (meant for Node.js console) and set signal port to 5000 instead of 21116, causing EADDRINUSE race condition. Fixed by adding `SIGNAL_PORT` env var with priority over `PORT` in `config.go`, setting `SIGNAL_PORT=21116` in `supervisord.conf` and `entrypoint.sh`, adding `ENV SIGNAL_PORT=21116` to `Dockerfile` — Phase 13
|
||||
24. ~~**`get_public_ip: command not found` (Issue #58)**~~ ✅ ROZWIĄZANE - Diagnostics function called undefined `get_public_ip` at line 3348. Created reusable `get_public_ip()` function (IPv4-first) in all 3 scripts, replaced all inline curl patterns. Added private IP warning + `RELAY_SERVERS` env var override in `setup_services()`. Go server `GetRelayServers()` now auto-appends relay port when missing — Phase 14
|
||||
25. ~~**Docker: Devices page 0 while Dashboard shows count (Issue #59)**~~ ✅ ROZWIĄZANE - Docker single-container never created `.api_key` file. Dashboard used public `/api/server/stats` (correct), Devices used protected `/api/peers` (401 → empty). Go server `loadAPIKey()` now auto-generates key on first run, Docker entrypoint also generates as safety net, Node.js `betterdeskApi.js` has 401-interceptor to reload key from file — Phase 16
|
||||
|
||||
---
|
||||
|
||||
@@ -706,4 +713,4 @@ All code changes MUST include a security review as part of the implementation pr
|
||||
|
||||
---
|
||||
|
||||
*Ostatnia aktualizacja: 2026-03-15 (Security hardening API + installers — Phase 15) przez GitHub Copilot*
|
||||
*Ostatnia aktualizacja: 2026-03-15 (Docker API key auto-generation — Phase 16) przez GitHub Copilot*
|
||||
|
||||
@@ -4,6 +4,8 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
cryptoRand "crypto/rand"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -378,9 +380,36 @@ func loadAPIKey(cfg *config.Config, database db.Database) {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check database server_config table (may have been set previously)
|
||||
if apiKey == "" {
|
||||
log.Printf("WARN: No API key found (no API_KEY env var, no .api_key file in key/db directory). Console→Server auth will fail.")
|
||||
return
|
||||
if existing, _ := database.GetConfig("api_key"); existing != "" {
|
||||
apiKey = existing
|
||||
source = "database server_config"
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Auto-generate if nothing found anywhere
|
||||
if apiKey == "" {
|
||||
b := make([]byte, 32)
|
||||
if _, err := cryptoRand.Read(b); err != nil {
|
||||
log.Printf("WARN: Failed to generate API key: %v. Console→Server auth will fail.", err)
|
||||
return
|
||||
}
|
||||
apiKey = hex.EncodeToString(b)
|
||||
source = "auto-generated"
|
||||
|
||||
// Write to key file directory so Node.js console can read it
|
||||
keyDir := filepath.Dir(cfg.KeyFile)
|
||||
if keyDir == "" || keyDir == "." {
|
||||
keyDir = "."
|
||||
}
|
||||
apiKeyFile := filepath.Join(keyDir, ".api_key")
|
||||
if err := os.WriteFile(apiKeyFile, []byte(apiKey+"\n"), 0600); err != nil {
|
||||
log.Printf("WARN: Auto-generated API key but failed to write %s: %v", apiKeyFile, err)
|
||||
// Still try to store in DB even if file write fails
|
||||
} else {
|
||||
log.Printf("Auto-generated API key written to %s", apiKeyFile)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync to database server_config table
|
||||
|
||||
@@ -66,6 +66,26 @@ fi
|
||||
# Ensure Go server uses correct signal port (not NODE.js PORT)
|
||||
export SIGNAL_PORT="${SIGNAL_PORT:-21116}"
|
||||
|
||||
# Ensure API key exists (shared between Go server and Node.js console)
|
||||
API_KEY_FILE="/opt/rustdesk/.api_key"
|
||||
if [ -z "${API_KEY:-}" ] && [ ! -f "$API_KEY_FILE" ]; then
|
||||
# Auto-generate a 32-byte hex API key
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
API_KEY=$(openssl rand -hex 32)
|
||||
else
|
||||
API_KEY=$(cat /dev/urandom | head -c 32 | od -An -tx1 | tr -d ' \n')
|
||||
fi
|
||||
echo "$API_KEY" > "$API_KEY_FILE"
|
||||
chmod 600 "$API_KEY_FILE"
|
||||
chown betterdesk:betterdesk "$API_KEY_FILE" 2>/dev/null || true
|
||||
echo "Auto-generated API key → $API_KEY_FILE"
|
||||
elif [ -n "${API_KEY:-}" ] && [ ! -f "$API_KEY_FILE" ]; then
|
||||
echo "$API_KEY" > "$API_KEY_FILE"
|
||||
chmod 600 "$API_KEY_FILE"
|
||||
chown betterdesk:betterdesk "$API_KEY_FILE" 2>/dev/null || true
|
||||
echo "API key from env → $API_KEY_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Starting services via supervisord..."
|
||||
echo " Web Console: http://localhost:${PORT:-5000}"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Lessons
|
||||
|
||||
- When user redirects focus to Discussions, pause issue triage and respond directly in the active discussion with concrete diagnostic steps.
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
const axios = require('axios');
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const config = require('../config/config');
|
||||
|
||||
// Axios instance for BetterDesk Go API
|
||||
@@ -23,6 +24,27 @@ const apiClient = axios.create({
|
||||
httpsAgent: new https.Agent({ rejectUnauthorized: false })
|
||||
});
|
||||
|
||||
// Retry once on 401 by reloading API key from file (handles race condition
|
||||
// where Go server generated the key after Node.js cached an empty value).
|
||||
let _keyReloaded = false;
|
||||
apiClient.interceptors.response.use(undefined, async (error) => {
|
||||
if (error.response?.status === 401 && !_keyReloaded) {
|
||||
_keyReloaded = true;
|
||||
try {
|
||||
const fresh = fs.readFileSync(config.apiKeyPath, 'utf8').trim();
|
||||
if (fresh && fresh !== config.betterdeskApiKey) {
|
||||
apiClient.defaults.headers['X-API-Key'] = fresh;
|
||||
config.betterdeskApiKey = fresh;
|
||||
console.log('API key reloaded from', config.apiKeyPath);
|
||||
// Retry the original request with new key
|
||||
error.config.headers['X-API-Key'] = fresh;
|
||||
return apiClient.request(error.config);
|
||||
}
|
||||
} catch (_) { /* file not found — nothing to reload */ }
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: normalise Go API flat responses into { success, data } shape
|
||||
// that the Node.js panel expects.
|
||||
|
||||
Reference in New Issue
Block a user