Skip to content

Commit ffbb4df

Browse files
committed
feat(treewide): lazy load resources from internet on first use
Now, nothing is dynamically downloaded from the internet at install time. Instead, on first use, a cache direcory is created (`~/.cache/mdn-http-observatory`) and these files downloaded: - Mozilla CA Bundle - HSTS preload list - TLD list When one of these files is not present, it is downloaded on the next invocation. When it is present on the filesystem it is used right away. "Refreshing" these resources can then simply be done by removing the cache directory and restarting the application. It can also be done by calling `npm run refreshCache` or via `npm run maintenance`. This enables packaging mdn-http-observatory for Linux distros. Additionaly, it makes the package a little easier to understand because there is no loading of resources at various points at install time.
1 parent 98e3079 commit ffbb4df

File tree

12 files changed

+172
-143
lines changed

12 files changed

+172
-143
lines changed

bin/wrapper.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@ import { fileURLToPath } from "node:url";
99
const __filename = fileURLToPath(import.meta.url);
1010
const __dirname = path.dirname(__filename);
1111

12-
// Set the environment variable for extra CA certificates
13-
let caCertPath = import.meta.resolve("node_extra_ca_certs_mozilla_bundle");
14-
caCertPath = new URL(caCertPath).pathname;
15-
caCertPath = path.dirname(caCertPath);
16-
caCertPath = path.join(caCertPath, "ca_bundle", "ca_intermediate_bundle.pem");
17-
process.env.NODE_EXTRA_CA_CERTS = caCertPath;
18-
1912
// The target script you want to run (relative to this script's directory)
2013
const targetScript = path.join(__dirname, "..", "src", "scan.js");
2114

package-lock.json

Lines changed: 7 additions & 73 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,14 @@
99
"npm": ">=9.0.0"
1010
},
1111
"scripts": {
12-
"start": "NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem node src/api/index.js",
13-
"dev": "NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem nodemon src/api/index.js",
12+
"start": "node src/api/index.js",
13+
"dev": "nodemon src/api/index.js",
1414
"test": "CONFIG_FILE=conf/config-test.json mocha",
1515
"tsc": "tsc -p jsconfig.json",
16-
"updateHsts": "node src/retrieve-hsts.js",
17-
"updateTldList": "node src/retrieve-tld-list.js",
1816
"refreshMaterializedViews": "node src/maintenance/index.js",
17+
"refreshCache": "node src/cache.js",
1918
"maintenance": "node src/maintenance/index.js",
20-
"migrate": "node -e 'import(\"./src/database/migrate.js\").then( m => m.migrateDatabase() )'",
21-
"postinstall": "npm run updateHsts && npm run updateTldList"
19+
"migrate": "node -e 'import(\"./src/database/migrate.js\").then( m => m.migrateDatabase() )'"
2220
},
2321
"bin": {
2422
"mdn-http-observatory-scan": "bin/wrapper.js"
@@ -59,7 +57,7 @@
5957
"http-cookie-agent": "^7.0.1",
6058
"ip": "^2.0.1",
6159
"jsdom": "^27.0.0",
62-
"node_extra_ca_certs_mozilla_bundle": "^1.0.7",
60+
"papaparse": "^5.5.3",
6361
"pg": "^8.16.2",
6462
"pg-format": "^1.0.4",
6563
"pg-native": "^3.5.2",

src/analyzer/hsts.js

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import fs from "fs";
2-
import path from "node:path";
3-
import { fileURLToPath } from "node:url";
42
import { Site } from "../site.js";
5-
6-
const dirname = path.dirname(fileURLToPath(import.meta.url));
3+
import { HSTS_PRELOAD_PATH } from "../cache.js";
74

85
/**
96
* @type {import("../types.js").Hsts | null}
@@ -15,15 +12,8 @@ let hstsMap = null;
1512
*/
1613
export function hsts() {
1714
if (!hstsMap) {
18-
const filePath = path.join(
19-
dirname,
20-
"..",
21-
"..",
22-
"conf",
23-
"hsts-preload.json"
24-
);
2515
hstsMap = new Map(
26-
Object.entries(JSON.parse(fs.readFileSync(filePath, "utf8")))
16+
Object.entries(JSON.parse(fs.readFileSync(HSTS_PRELOAD_PATH, "utf8")))
2717
);
2818
}
2919
return hstsMap;

src/api/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { CONFIG } from "../config.js";
22
import { createServer } from "./server.js";
3+
import { setupCache } from "../cache.js";
34

45
async function main() {
6+
await setupCache();
7+
58
const server = await createServer();
69
try {
710
await server.listen({

src/api/v2/utils.js

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import ip from "ip";
22
import dns from "node:dns";
33
import fs from "fs";
4-
import { fileURLToPath } from "node:url";
5-
import path from "node:path";
64
import {
75
InvalidHostNameError,
86
InvalidHostNameIpError,
@@ -28,6 +26,7 @@ import { PolicyResponse } from "./schemas.js";
2826
import { Expectation } from "../../types.js";
2927
import { TEST_TITLES } from "../../grader/charts.js";
3028
import { scan } from "../../scanner/index.js";
29+
import { TLD_LIST_PATH } from "../../cache.js";
3130

3231
/**
3332
*
@@ -50,23 +49,14 @@ export function isIp(hostname) {
5049
* @type {Set<string> | null}
5150
*/
5251
let tldSet = null;
53-
const dirname = path.dirname(fileURLToPath(import.meta.url));
5452

5553
/**
5654
* Get the cached set of top level domains.
5755
* @returns {Set<string>}
5856
*/
5957
function tlds() {
6058
if (!tldSet) {
61-
const filePath = path.join(
62-
dirname,
63-
"..",
64-
"..",
65-
"..",
66-
"conf",
67-
"tld-list.json"
68-
);
69-
tldSet = new Set(JSON.parse(fs.readFileSync(filePath, "utf8")));
59+
tldSet = new Set(JSON.parse(fs.readFileSync(TLD_LIST_PATH, "utf8")));
7060
}
7161
return tldSet;
7262
}

src/ca-bundle.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import axios from "axios";
2+
import { writeFile } from "fs/promises";
3+
import Papa from "papaparse";
4+
5+
const INTERMEDIATE_CA_URL =
6+
"https://ccadb.my.salesforce-sites.com/mozilla/PublicAllIntermediateCertsWithPEMCSV";
7+
8+
const ROOT_CA_URL =
9+
"https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV";
10+
11+
/**
12+
* @param {string} url
13+
* @returns {Promise<string[]>}
14+
*/
15+
async function downloadCertificates(url) {
16+
let r;
17+
try {
18+
r = await axios.get(url);
19+
} catch (error) {
20+
throw Error(`Failed to get data: ${error}`);
21+
}
22+
23+
const data = Papa.parse(r.data, { header: true }).data;
24+
const output = [];
25+
for (const entry of data) {
26+
// Remove quotes from beginning and end of certificate
27+
const certPem = entry["PEM Info"].slice(1, -1);
28+
const commonName = entry["Common Name or Certificate Name"];
29+
output.push(`${commonName}\n${certPem}`);
30+
}
31+
return output;
32+
}
33+
34+
/**
35+
* @returns {Promise<string>}
36+
*/
37+
async function retrieveCABundle() {
38+
// Download at the same time
39+
const values = await Promise.all([
40+
downloadCertificates(INTERMEDIATE_CA_URL),
41+
downloadCertificates(ROOT_CA_URL),
42+
]);
43+
44+
const intermediateCACerts = values[0];
45+
const rootCACerts = values[1];
46+
47+
const combinedCACerts = intermediateCACerts.concat(rootCACerts);
48+
return combinedCACerts.join("\n\n");
49+
}
50+
51+
/**
52+
* @param {string} filePath
53+
*/
54+
export async function retrieveAndStoreCABundle(filePath) {
55+
const caBundle = await retrieveCABundle();
56+
57+
try {
58+
await writeFile(filePath, caBundle);
59+
console.log(`Downloaded Mozilla CA bundle and saved it to ${filePath}`);
60+
} catch (error) {
61+
console.error("Error writing file:", error);
62+
return;
63+
}
64+
}
65+
66+
/**
67+
*
68+
* @param {string} filePath
69+
*/
70+
export async function setupCABundle(filePath) {
71+
process.env.NODE_EXTRA_CA_CERTS = filePath;
72+
}

0 commit comments

Comments
 (0)