fix SSH login alert delivery (#1100)

This commit is contained in:
ZacharyZcR
2026-07-28 01:47:54 +08:00
committed by GitHub
parent 6d790b8d61
commit 1139f17319
3 changed files with 94 additions and 37 deletions
+30 -30
View File
@@ -857,6 +857,36 @@ app.use((_req, res, next) => {
next();
});
// Internal endpoint — only accepts calls from localhost.
// Used by the main backend to notify the metrics service of SSH login events.
app.post("/internal/login-alert", async (req, res) => {
const remoteIp = req.socket.remoteAddress;
if (
remoteIp !== "127.0.0.1" &&
remoteIp !== "::1" &&
remoteIp !== "::ffff:127.0.0.1"
) {
return res.status(403).json({ error: "Forbidden" });
}
const systemCrypto = (await import("../../utils/system-crypto.js"))
.SystemCrypto;
const expectedToken = await systemCrypto.getInstance().getInternalAuthToken();
const token = req.headers["x-internal-auth"];
if (!token || token !== expectedToken) {
return res.status(403).json({ error: "Forbidden" });
}
const { hostId, userId, sshUser, fromIp } = req.body as {
hostId: number;
userId: string;
sshUser: string;
fromIp: string;
};
AlertEngine.getInstance()
.evaluateUserLogin(hostId, userId, sshUser, fromIp)
.catch(() => {});
res.json({ ok: true });
});
app.use(authManager.createAuthMiddleware());
const requireAdmin = authManager.createAdminMiddleware();
@@ -2803,36 +2833,6 @@ registerManagerRoutes(app, {
},
});
// Internal endpoint — only accepts calls from localhost.
// Used by the main backend to notify the metrics service of SSH login events.
app.post("/internal/login-alert", async (req, res) => {
const remoteIp = req.socket.remoteAddress;
if (
remoteIp !== "127.0.0.1" &&
remoteIp !== "::1" &&
remoteIp !== "::ffff:127.0.0.1"
) {
return res.status(403).json({ error: "Forbidden" });
}
const systemCrypto = (await import("../../utils/system-crypto.js"))
.SystemCrypto;
const expectedToken = await systemCrypto.getInstance().getInternalAuthToken();
const token = req.headers["x-internal-auth"];
if (!token || token !== expectedToken) {
return res.status(403).json({ error: "Forbidden" });
}
const { hostId, userId, sshUser, fromIp } = req.body as {
hostId: number;
userId: string;
sshUser: string;
fromIp: string;
};
AlertEngine.getInstance()
.evaluateUserLogin(hostId, userId, sshUser, fromIp)
.catch(() => {});
res.json({ ok: true });
});
process.on("SIGINT", () => {
pollingManager.destroy();
connectionPool.destroy();
@@ -0,0 +1,48 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { triggerLoginAlert } from "../../utils/alert-trigger.js";
import { SystemCrypto } from "../../utils/system-crypto.js";
import { sshLogger } from "../../utils/logger.js";
describe("triggerLoginAlert", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("reports a rejected metrics-service request", async () => {
vi.spyOn(SystemCrypto, "getInstance").mockReturnValue({
getInternalAuthToken: vi.fn().mockResolvedValue("internal-token"),
} as never);
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response('{"error":"Missing authentication token"}', {
status: 401,
}),
);
const warn = vi.spyOn(sshLogger, "warn").mockImplementation(() => {});
await triggerLoginAlert(7, "user-1", "root", "192.0.2.1");
expect(warn).toHaveBeenCalledWith(
"Failed to trigger login alert",
expect.objectContaining({
operation: "login_alert_trigger_error",
hostId: 7,
error:
'Metrics service returned 401: {"error":"Missing authentication token"}',
}),
);
});
it("does not log a warning when the metrics service accepts the event", async () => {
vi.spyOn(SystemCrypto, "getInstance").mockReturnValue({
getInternalAuthToken: vi.fn().mockResolvedValue("internal-token"),
} as never);
vi.spyOn(globalThis, "fetch").mockResolvedValue(
new Response('{"ok":true}', { status: 200 }),
);
const warn = vi.spyOn(sshLogger, "warn").mockImplementation(() => {});
await triggerLoginAlert(7, "user-1", "root", "192.0.2.1");
expect(warn).not.toHaveBeenCalled();
});
});
+16 -7
View File
@@ -11,14 +11,23 @@ export async function triggerLoginAlert(
): Promise<void> {
try {
const token = await SystemCrypto.getInstance().getInternalAuthToken();
await fetch(`${METRICS_SERVICE_URL}/internal/login-alert`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-internal-auth": token,
const response = await fetch(
`${METRICS_SERVICE_URL}/internal/login-alert`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-internal-auth": token,
},
body: JSON.stringify({ hostId, userId, sshUser, fromIp }),
},
body: JSON.stringify({ hostId, userId, sshUser, fromIp }),
});
);
if (!response.ok) {
const details = await response.text();
throw new Error(
`Metrics service returned ${response.status}${details ? `: ${details}` : ""}`,
);
}
} catch (err) {
sshLogger.warn("Failed to trigger login alert", {
operation: "login_alert_trigger_error",