♻️🐛 back: removing heap roundtrip and minor cleanup (#762)

This commit is contained in:
Eric Doughty-Papassideris
2024-12-12 04:07:39 +01:00
parent 7da9062916
commit 4da82ecfcc
7 changed files with 5 additions and 83 deletions
@@ -21,8 +21,7 @@
"__name": "DIAG_SKIP_KEYS", "__name": "DIAG_SKIP_KEYS",
"__format": "json" "__format": "json"
}, },
"probeSecret": "DIAG_PROBE_SECRET", "probeSecret": "DIAG_PROBE_SECRET"
"secret": "DIAG_SECRET"
}, },
"webserver": { "webserver": {
"host": "TWAKE_DRIVE_HOST", "host": "TWAKE_DRIVE_HOST",
+1 -3
View File
@@ -37,9 +37,7 @@
}, },
"diagnostics": { "diagnostics": {
"skipKeys": [], "skipKeys": [],
"probeSecret": "", "probeSecret": ""
"secret": "",
"secretChallengeRefreshS": 15
}, },
"tracker": { "tracker": {
"type": "segment", "type": "segment",
@@ -44,13 +44,6 @@ interface IDiagnosticsConfig {
// relied on for security because disabling diagnostics. At worst this // relied on for security because disabling diagnostics. At worst this
// provides access to the DB statistics. // provides access to the DB statistics.
probeSecret?: string; probeSecret?: string;
// This secret is required to sign more dangerous diagnostic endpoints, such
// the heap snapshot. It should never be sent over the network.
secret?: string;
// Maximum time to keep the same challenge token for diagnostic endpoints, in seconds.
// Must be large enough to have a reasonable chance of running the token request
// then the action on the same backend instance.
secretChallengeRefreshS: number;
} }
export const getConfig = (): IDiagnosticsConfig => { export const getConfig = (): IDiagnosticsConfig => {
@@ -63,11 +56,6 @@ export const getConfig = (): IDiagnosticsConfig => {
.split(/[,\s]+/g) .split(/[,\s]+/g)
.filter(x => !!x), .filter(x => !!x),
}; };
if (typeof configSection.secretChallengeRefreshS === "string")
configSection = {
...configSection,
secretChallengeRefreshS: parseInt(configSection.secretChallengeRefreshS, 10),
};
return configSection; return configSection;
}; };
@@ -94,8 +82,6 @@ export interface IDiagnosticProvider {
/** /**
* Returns an object as presented to a diagnostic requester. * Returns an object as presented to a diagnostic requester.
* Warning: this could be public and readable to the internet. * Warning: this could be public and readable to the internet.
* @param completeButSlow If `true`, perform additional operations for a
* more informative
*/ */
get(): Promise<TDiagnosticResult>; get(): Promise<TDiagnosticResult>;
} }
@@ -1,71 +1,13 @@
import type { FastifyInstance, FastifyPluginCallback } from "fastify"; import type { FastifyInstance, FastifyPluginCallback } from "fastify";
import { getConfig as getDiagnosticsGetConfig } from "../../../framework/api/diagnostics"; import { getConfig as getDiagnosticsGetConfig } from "../../../framework/api/diagnostics";
import { getHeapSnapshotSync } from "../utils"; import { getHeapSnapshotSync } from "../utils";
import { createHash, randomUUID } from "node:crypto";
const routes: FastifyPluginCallback = (fastify: FastifyInstance, _opts, next) => { const routes: FastifyPluginCallback = (fastify: FastifyInstance, _opts, next) => {
const diagnosticsConfig = getDiagnosticsGetConfig(); const diagnosticsConfig = getDiagnosticsGetConfig();
if (diagnosticsConfig?.probeSecret?.length) {
if (diagnosticsConfig?.probeSecret?.length && diagnosticsConfig?.secret?.length) { fastify.post("/heap", async (request, reply) => {
const getRunningToken = (() => {
const newToken = () => [randomUUID(), randomUUID()].join("*");
let token = newToken();
let updatedS = process.uptime();
return () => {
const nowS = process.uptime();
if (nowS - updatedS > diagnosticsConfig.secretChallengeRefreshS) {
token = newToken();
updatedS = nowS;
}
return token;
};
})();
const hashToken = (token: string = getRunningToken()) =>
createHash("sha512").update(`+${token}+${diagnosticsConfig.secret}+`).digest("hex");
/*
Example flow:
Configuration:
{ secret: 'secretValue', probeSecret: 'probeSecretValue' }
GET current token using the probeSecret:
$ curl "$SERVER/api/diagnostics/heap?secret=probeSecretValue"
{"token":"d5888f91-d929-4775-b9dc-de6e2fb4d7cd*b2a57b32-7029-4957-82e9-c7c25794727f"}
Hash `+${token}+${secret}+` in SHA-512:
Shell example:
$ echo -n '+d5888f91-d929-4775-b9dc-de6e2fb4d7cd*b2a57b32-7029-4957-82e9-c7c25794727f+secretValue+' | sha512sum | cut -f1 -d' '
320070a508da218baf0f3363e837080fdb902bd0d986bd0f33b806b1230608c0868accdd10bd25261ca91b57c0459edf76218deb26571c72f6b93b077846abe3
JS example:
require('crypto').createHash('sha512').update(`+${token}+${secret}+`).digest('hex')
POST to download snapshot:
$ curl "$SERVER/api/diagnostics/heap"
Shell script example to download heap snapshot (requires curl, node and jq):
set -euo pipefail
SERVER="http://localhost:4000"
DIAG_PROBE_SECRET="probeSecretValue"
DIAG_SECRET="secretValue"
urlencode() { node -e 'console.log(encodeURIComponent(process.argv[1]))' "$@" ; }
DIAG_PROBE_SECRET_URLENCODED="$(urlencode "$DIAG_PROBE_SECRET")"
TOKEN="$(curl --fail-with-body "$SERVER/api/diagnostics/heap?secret=$DIAG_PROBE_SECRET_URLENCODED" | jq -r .token)"
HASHED="$(echo -n "+$TOKEN+$DIAG_SECRET+" | sha512sum | cut -f1 -d' ')"
curl --fail-with-body --remote-name --remote-header-name \
--header "Content-Type: application/json" \
--data '{"hash":"'"$HASHED"'"}' \
"$SERVER/api/diagnostics/heap"
# The file should be downloaded as `twake-drive-snap-$date.heapsnapshot`
ls twake-drive-snap-*.heapsnapshot
*/
fastify.get("/heap", async (request, reply) => {
if ((request.query as { secret: string }).secret !== diagnosticsConfig.probeSecret) if ((request.query as { secret: string }).secret !== diagnosticsConfig.probeSecret)
return reply.status(403).send(); return reply.status(403).send();
return reply.send({ token: getRunningToken() });
});
fastify.post("/heap", async (request, reply) => {
const hashExpected = hashToken();
const hashProvided = (request.body as { hash: string })?.hash;
if (hashProvided !== hashExpected) return reply.status(403).send();
const filenameTimestamp = new Date() const filenameTimestamp = new Date()
.toISOString() .toISOString()
.replace(/(\.\d\d\d)?Z$/, "") .replace(/(\.\d\d\d)?Z$/, "")
@@ -79,7 +21,6 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _opts, next) =>
return replyResult; return replyResult;
}); });
} }
next(); next();
}; };
@@ -36,7 +36,7 @@ export default class LocalConnectorService implements StorageConnectorAPI {
case TServiceDiagnosticDepth.stats_basic: case TServiceDiagnosticDepth.stats_basic:
case TServiceDiagnosticDepth.stats_track: case TServiceDiagnosticDepth.stats_track:
case TServiceDiagnosticDepth.stats_deep: case TServiceDiagnosticDepth.stats_deep:
return { ok: true, warn: "local_s3_alway_ok" }; return { ok: true, warn: "local_storage_always_ok" };
default: default:
throw new Error(`Unexpected TServiceDiagnosticDepth: ${JSON.stringify(depth)}`); throw new Error(`Unexpected TServiceDiagnosticDepth: ${JSON.stringify(depth)}`);
@@ -69,7 +69,6 @@ services:
- SEARCH_OS_USE_AUTH=true - SEARCH_OS_USE_AUTH=true
- SEARCH_OS_USERNAME=admin - SEARCH_OS_USERNAME=admin
- DIAG_PROBE_SECRET=super_diagnostic_probe_secret - DIAG_PROBE_SECRET=super_diagnostic_probe_secret
- DIAG_SECRET=super_diagnostic_secret
depends_on: depends_on:
- postgres - postgres
- opensearch-node1 - opensearch-node1
-1
View File
@@ -85,7 +85,6 @@ services:
- STORAGE_S3_ACCESS_KEY=admin - STORAGE_S3_ACCESS_KEY=admin
- STORAGE_S3_SECRET_KEY=adminminio - STORAGE_S3_SECRET_KEY=adminminio
- DIAG_PROBE_SECRET=super_diagnostic_probe_secret - DIAG_PROBE_SECRET=super_diagnostic_probe_secret
- DIAG_SECRET=super_diagnostic_secret
depends_on: depends_on:
minio: minio:
condition: service_healthy condition: service_healthy