This repository has been archived on 2022-10-07. You can view files and clone it, but cannot push or open issues or pull requests.
skynet-webportal/packages/health-check/src/checks/critical.js

240 lines
8.5 KiB
JavaScript
Raw Normal View History

2021-04-27 16:30:51 +00:00
const got = require("got");
const FormData = require("form-data");
const { isEqual } = require("lodash");
const { calculateElapsedTime, getResponseContent, getAuthCookie, isPortalModuleEnabled } = require("../utils");
2021-10-28 13:12:14 +00:00
const { SkynetClient, stringToUint8ArrayUtf8, genKeyPairAndSeed } = require("skynet-js");
2021-05-24 14:27:30 +00:00
2022-01-17 13:04:55 +00:00
const MODULE_BLOCKER = "b";
2022-01-17 12:54:12 +00:00
2022-02-16 13:13:37 +00:00
const skynetClient = new SkynetClient(`https://${process.env.PORTAL_DOMAIN}`);
2021-05-24 14:27:30 +00:00
const exampleSkylink = "AACogzrAimYPG42tDOKhS3lXZD8YvlF8Q8R17afe95iV2Q";
2020-09-09 12:25:00 +00:00
// check that any relevant configuration is properly set in skyd
async function skydConfigCheck(done) {
const time = process.hrtime();
const data = { up: false };
try {
const response = await got(`http://10.10.10.10:9980/renter`, { headers: { "User-Agent": "Sia-Agent" } }).json();
// make sure initial funding is set to 10SC
if (response.settings.allowance.paymentcontractinitialfunding !== "10000000000000000000000000") {
2021-10-26 16:18:56 +00:00
throw new Error("Skynet Portal Per-Contract Budget is not set correctly!");
}
data.up = true;
data.ip = response.ip;
} catch (error) {
data.statusCode = error.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
}
done({ name: "skyd_config", time: calculateElapsedTime(time), ...data });
}
2020-09-09 12:25:00 +00:00
// uploadCheck returns the result of uploading a sample file
async function uploadCheck(done) {
const authCookie = await getAuthCookie();
2020-09-09 12:25:00 +00:00
const time = process.hrtime();
2021-04-27 16:30:51 +00:00
const form = new FormData();
const payload = Buffer.from(new Date()); // current date to ensure data uniqueness
const data = { up: false };
2021-04-27 16:30:51 +00:00
form.append("file", payload, { filename: "time.txt", contentType: "text/plain" });
2021-04-27 16:30:51 +00:00
try {
2022-02-16 13:13:37 +00:00
const response = await got.post(`https://${process.env.PORTAL_DOMAIN}/skynet/skyfile`, {
body: form,
headers: { cookie: authCookie },
});
2021-04-27 16:30:51 +00:00
data.statusCode = response.statusCode;
data.up = true;
data.ip = response.ip;
2021-04-27 16:30:51 +00:00
} catch (error) {
data.statusCode = error.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
2021-04-27 16:30:51 +00:00
}
2021-05-24 14:27:30 +00:00
done({ name: "upload_file", time: calculateElapsedTime(time), ...data });
}
// websiteCheck checks whether the main website is working
async function websiteCheck(done) {
2022-02-16 13:13:37 +00:00
return done(await genericAccessCheck("website", `https://${process.env.PORTAL_DOMAIN}`));
2020-09-09 12:25:00 +00:00
}
// downloadCheck returns the result of downloading the hard coded link
async function downloadCheck(done) {
2021-05-24 14:27:30 +00:00
const url = await skynetClient.getSkylinkUrl(exampleSkylink);
2021-06-16 14:26:36 +00:00
return done(await genericAccessCheck("skylink", url));
2021-05-24 14:27:30 +00:00
}
// skylinkSubdomainCheck returns the result of downloading the hard coded link via subdomain
async function skylinkSubdomainCheck(done) {
const url = await skynetClient.getSkylinkUrl(exampleSkylink, { subdomain: true });
2021-06-16 14:26:36 +00:00
return done(await genericAccessCheck("skylink_via_subdomain", url));
2021-05-24 14:27:30 +00:00
}
2021-05-24 15:07:37 +00:00
// handshakeSubdomainCheck returns the result of downloading the skylink via handshake domain
2021-05-24 14:27:30 +00:00
async function handshakeSubdomainCheck(done) {
const url = await skynetClient.getHnsUrl("note-to-self", { subdomain: true });
2021-06-16 14:26:36 +00:00
return done(await genericAccessCheck("hns_via_subdomain", url));
2021-05-24 14:27:30 +00:00
}
2021-05-24 15:30:29 +00:00
// accountWebsiteCheck returns the result of accessing account dashboard website
2021-05-24 15:07:37 +00:00
async function accountWebsiteCheck(done) {
2022-02-09 13:13:53 +00:00
const url = `https://account.${process.env.PORTAL_DOMAIN}/auth/login`;
2021-05-24 15:12:16 +00:00
2021-06-16 14:26:36 +00:00
return done(await genericAccessCheck("account_website", url));
}
// registryWriteAndReadCheck writes to registry and immediately reads and compares the data
async function registryWriteAndReadCheck(done) {
const authCookie = await getAuthCookie();
const time = process.hrtime();
const data = { name: "registry_write_and_read", up: false };
const { privateKey, publicKey } = genKeyPairAndSeed();
2021-10-28 13:12:14 +00:00
const expected = { dataKey: "foo-key", data: stringToUint8ArrayUtf8("foo-data"), revision: BigInt(0) };
try {
await skynetClient.registry.setEntry(privateKey, expected, { customCookie: authCookie });
const { entry } = await skynetClient.registry.getEntry(publicKey, expected.dataKey, { customCookie: authCookie });
if (isEqual(expected, entry)) {
data.up = true;
} else {
data.errors = [{ message: "Data mismatch in registry (read after write)", entry, expected }];
}
} catch (error) {
2021-10-28 13:12:14 +00:00
data.errors = [{ message: error?.response?.data?.message ?? error.message }];
}
return done({ ...data, time: calculateElapsedTime(time) });
}
2021-06-16 14:26:36 +00:00
// directServerApiAccessCheck returns the basic server api check on direct server address
async function directServerApiAccessCheck(done) {
// skip if SERVER_DOMAIN is not set or it equals PORTAL_DOMAIN (single server portals)
if (!process.env.SERVER_DOMAIN || process.env.SERVER_DOMAIN === process.env.PORTAL_DOMAIN) {
return done();
2021-06-16 14:26:36 +00:00
}
2021-06-18 10:57:44 +00:00
const [portalAccessCheck, serverAccessCheck] = await Promise.all([
2022-02-16 13:13:37 +00:00
genericAccessCheck("portal_api_access", `https://${process.env.PORTAL_DOMAIN}`),
genericAccessCheck("server_api_access", `https://${process.env.SERVER_DOMAIN}`),
2021-06-16 14:26:36 +00:00
]);
2021-06-18 10:57:44 +00:00
if (portalAccessCheck.ip !== serverAccessCheck.ip) {
serverAccessCheck.up = false;
2021-06-22 12:03:07 +00:00
serverAccessCheck.errors = serverAccessCheck.errors ?? [];
serverAccessCheck.errors.push({
2021-06-18 10:58:36 +00:00
message: "Access ip mismatch between portal and server access",
2021-06-16 14:26:36 +00:00
response: {
2022-02-16 13:13:37 +00:00
portal: { name: process.env.PORTAL_DOMAIN, ip: portalAccessCheck.ip },
server: { name: process.env.SERVER_DOMAIN, ip: serverAccessCheck.ip },
2021-06-16 14:26:36 +00:00
},
2021-06-22 12:03:07 +00:00
});
2021-06-16 14:26:36 +00:00
}
2021-06-18 10:57:44 +00:00
return done(serverAccessCheck);
2021-05-24 15:07:37 +00:00
}
// accountHealthCheck returns the result of accounts service health checks
2021-05-24 14:27:30 +00:00
async function accountHealthCheck(done) {
2020-09-09 12:25:00 +00:00
const time = process.hrtime();
const data = { up: false };
2020-09-09 12:25:00 +00:00
try {
2022-02-09 13:13:53 +00:00
const response = await got(`https://account.${process.env.PORTAL_DOMAIN}/health`, { responseType: "json" });
2020-09-09 12:25:00 +00:00
data.statusCode = response.statusCode;
2021-05-24 14:27:30 +00:00
data.response = response.body;
data.up = response.body.dbAlive === true;
data.ip = response.ip;
2020-09-09 12:25:00 +00:00
} catch (error) {
data.statusCode = error?.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
2020-09-09 12:25:00 +00:00
}
2021-05-24 15:07:37 +00:00
done({ name: "accounts", time: calculateElapsedTime(time), ...data });
2020-09-09 12:25:00 +00:00
}
2022-01-17 11:18:03 +00:00
// blockerHealthCheck returns the result of blocker container health endpoint
async function blockerHealthCheck(done) {
const time = process.hrtime();
const data = { up: false };
try {
2022-01-17 13:04:55 +00:00
const response = await got(`http://${process.env.BLOCKER_HOST}:${process.env.BLOCKER_PORT}/health`, {
responseType: "json",
});
2022-01-17 11:18:03 +00:00
data.statusCode = response.statusCode;
data.response = response.body;
data.up = response.body.dbAlive === true;
} catch (error) {
data.statusCode = error?.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
}
2022-01-17 12:40:39 +00:00
// this is a no-op but it's added to explicitly document the ip property
// should not be set on the data object to prevent the IP from being compared
// to the server's IP - this is not required for this check and will fail
2022-01-17 13:04:55 +00:00
delete data.ip;
2022-01-17 12:40:39 +00:00
2022-01-17 11:18:03 +00:00
done({ name: "blocker", time: calculateElapsedTime(time), ...data });
}
2021-06-16 14:26:36 +00:00
async function genericAccessCheck(name, url) {
const authCookie = await getAuthCookie();
const time = process.hrtime();
2021-05-24 14:27:30 +00:00
const data = { up: false, url };
try {
const response = await got(url, { headers: { cookie: `nocache=true;${authCookie}` } });
data.statusCode = response.statusCode;
2021-05-24 14:27:30 +00:00
data.up = true;
data.ip = response.ip;
} catch (error) {
data.statusCode = error?.response?.statusCode || error.statusCode || error.status;
data.errorMessage = error.message;
data.errorResponseContent = getResponseContent(error.response);
data.ip = error?.response?.ip ?? null;
}
2021-06-16 14:26:36 +00:00
return { name, time: calculateElapsedTime(time), ...data };
}
2021-06-16 14:26:36 +00:00
const checks = [
skydConfigCheck,
2021-06-16 14:26:36 +00:00
uploadCheck,
websiteCheck,
downloadCheck,
skylinkSubdomainCheck,
handshakeSubdomainCheck,
registryWriteAndReadCheck,
2021-06-16 14:26:36 +00:00
directServerApiAccessCheck,
];
2021-07-26 10:31:12 +00:00
if (process.env.ACCOUNTS_ENABLED === "true") {
2021-05-24 15:07:37 +00:00
checks.push(accountHealthCheck, accountWebsiteCheck);
}
2022-01-17 12:54:12 +00:00
if (isPortalModuleEnabled(MODULE_BLOCKER)) {
2022-01-17 11:18:03 +00:00
checks.push(blockerHealthCheck);
}
module.exports = checks;