Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move tar outside of dependencies #338

Merged
merged 17 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
517545c
Move cmake-js and tar to devDeps
trivikr Jul 12, 2022
4d173d7
Merge branch 'move-tar-cmake-outside' of https://github.com/trivikr/a…
TwistedTwigleg Jul 13, 2022
e291900
Regenerated the package lock after merging changes to main
TwistedTwigleg Jul 13, 2022
3fc6f15
Merge branch 'main' of https://github.com/awslabs/aws-crt-nodejs into…
TwistedTwigleg Jul 15, 2022
893f8cf
Adjust build script to download cmakejs and tar only if they are not …
TwistedTwigleg Jul 15, 2022
b8bdeb5
Fix LGTM warning
TwistedTwigleg Jul 15, 2022
8ddd8e3
Add cmake-js back as a dependency
TwistedTwigleg Jul 15, 2022
b94c2f8
Merge branch 'main' of https://github.com/awslabs/aws-crt-nodejs into…
TwistedTwigleg Jul 15, 2022
8ac1fee
Add a try-catch around downloading tar
TwistedTwigleg Jul 15, 2022
84a9b6d
Initial version of having a build-specific set of NPM dependencies
TwistedTwigleg Jul 18, 2022
b8a9e96
Documented axios build step file, adjusted code to look for version a…
TwistedTwigleg Jul 19, 2022
0d2a3b0
Merge branch 'main' into move-tar-cmake-outside
TwistedTwigleg Jul 19, 2022
47e1063
Fix LGTM warnings by removing unused imports
TwistedTwigleg Jul 19, 2022
e583e28
Code review changes and general code cleanup
TwistedTwigleg Jul 19, 2022
c5b8868
Fix copy-paste error in tar build step
TwistedTwigleg Jul 20, 2022
f65dfc4
Specify the version of tar to use
TwistedTwigleg Jul 20, 2022
e9382cf
Move the runtime dependencies to devDependencies and get the version …
TwistedTwigleg Jul 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,805 changes: 843 additions & 962 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
"version": "1.0.0-dev",
"description": "NodeJS/browser bindings to the aws-c-* libraries",
"homepage": "https://github.com/awslabs/aws-crt-nodejs",
"repository": "github:awslabs/aws-crt-nodejs",
"repository": {
"type": "git",
"url": "git+https://github.com/awslabs/aws-crt-nodejs.git"
},
"contributors": [
"AWS Common Runtime Team <[email protected]>"
],
Expand Down Expand Up @@ -46,9 +49,7 @@
"@aws-sdk/util-utf8-browser": "^3.109.0",
"@httptoolkit/websocket-stream": "^6.0.0",
"axios": "^0.24.0",
"cmake-js": "6.3.0",
"crypto-js": "^4.0.0",
"mqtt": "^4.3.7",
"tar": "^6.1.11"
"mqtt": "^4.3.7"
}
}
131 changes: 9 additions & 122 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
const os = require('os');
const fs = require("fs");
const crypto = require('crypto');
const process = require("process");
const path = require("path");

const cmake = require("cmake-js");
const axios = require("axios");
const tar = require('tar');
const build_step_tar = require("./build_dependencies/build_step_tar");
const build_step_cmake = require("./build_dependencies/build_step_cmake");

function rmRecursive(rmPath) {
let rmBasePath = path.basename(rmPath);
if (rmBasePath == "." || rmBasePath == "..") {
Expand All @@ -36,120 +35,8 @@ function rmRecursive(rmPath) {
}
};

function downloadFile(fileUrl, outputLocationPath) {
const writer = fs.createWriteStream(outputLocationPath);
return axios({
method: 'get',
url: fileUrl,
responseType: 'stream',
}).then(response => {
return new Promise((resolve, reject) => {
response.data.pipe(writer);
let error = null;
writer.on('error', err => {
error = err;
writer.close();
reject(err);
});
writer.on('close', () => {
if (!error) {
resolve();
}
});
});
});
}

function checkChecksum(url, local_file) {
return axios({
method: 'get',
url: url,
responseType: 'text',
}).then(response => {
return new Promise((resolve, reject) => {
const filestream = fs.createReadStream(local_file);
const hash = crypto.createHash('sha256');
filestream.on('readable', () => {
// Only one element is going to be produced by the
// hash stream.
const data = filestream.read();
if (data)
hash.update(data);
else {
const checksum = hash.digest("hex")
if (checksum === response.data) {
resolve()
}
else {
reject(new Error("source code checksum mismatch"))
}
}
});
});
})
}

async function fetchNativeCode(url, version, path) {
const sourceURL = `${url}/aws-crt-${version}-source.tgz`
const tarballPath = path + "source.tgz";
await downloadFile(sourceURL, tarballPath);
const sourceChecksumURL = `${url}/aws-crt-${version}-source.sha256`;
await checkChecksum(sourceChecksumURL, tarballPath);
await tar.x({ file: tarballPath, strip: 2, C: nativeSourceDir });
}

function buildLocally() {
const platform = os.platform();
let arch = os.arch();

// Allow cross-compile (so OSX can do arm64 or x64 builds) via:
// --target-arch ARCH
if (process.argv.includes('--target-arch')) {
arch = process.argv[process.argv.indexOf('--target-arch') + 1];
}

// options for cmake.BuildSystem
let options = {
target: "install",
debug: process.argv.includes('--debug'),
arch: arch,
out: path.join('build', `${platform}-${arch}`),
cMakeOptions: {
CMAKE_EXPORT_COMPILE_COMMANDS: true,
CMAKE_JS_PLATFORM: platform,
BUILD_TESTING: 'OFF',
CMAKE_INSTALL_PREFIX: 'crt/install',
CMAKE_PREFIX_PATH: 'crt/install',
}
}

// We need to pass some extra flags to pull off cross-compiling
// because cmake-js doesn't set everything we need.
//
// See the docs on `arch`: https://github.com/cmake-js/cmake-js/blob/v6.1.0/README.md?#runtimes
// > Notice: on non-Windows systems the C++ toolset's architecture's gonna be used despite this setting.
if (platform === 'darwin') {
// What Node calls "x64", Apple calls "x86_64". They both agree on the term "arm64" though.
options.cMakeOptions.CMAKE_OSX_ARCHITECTURES = (arch === 'x64') ? 'x86_64' : arch;
}

// Convert any -D arguments to this script to cmake -D arguments
for (const arg of process.argv) {
if (arg.startsWith('-D')) {
const option = arg.substring(2).split('=')
options.cMakeOptions[option[0]] = option[1]
}
}

// Enable parallel build (ignored by cmake older than 3.12)
process.env.CMAKE_BUILD_PARALLEL_LEVEL = `${Math.max(os.cpus().length, 1)}`;

// Run the build
var buildSystem = new cmake.BuildSystem(options);
return buildSystem.build();
}

async function buildFromRemoteSource(tmpPath) {

if (fs.existsSync(nativeSourceDir)) {
//teardown the local source code
rmRecursive(nativeSourceDir);
Expand All @@ -164,11 +51,12 @@ async function buildFromRemoteSource(tmpPath) {
}
let rawData = fs.readFileSync('package.json');
const version = JSON.parse(rawData)["version"];
await fetchNativeCode(host, version, tmpPath);
// Get the file using tar, loaded as an install-time dependency (see scripts/build_dependencies)
await build_step_tar.performStep(host, version, tmpPath);
// Clean up temp directory
rmRecursive(tmpPath);
// Kick off local build
await buildLocally();
// Kick off local build using cmake-js loaded as an install-time dependency (see scripts/build_dependencies)
await build_step_cmake.performStep();
// Local build finished successfully, we don't need source anymore.
rmRecursive(nativeSourceDir);
}
Expand Down Expand Up @@ -197,6 +85,5 @@ if (checkDoDownload()) {
throw err;
}
} else {
// Kick off local build
buildLocally();
build_step_cmake.performStep();
}
106 changes: 106 additions & 0 deletions scripts/build_dependencies/build_step_axios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
const fs = require("fs");
const crypto = require('crypto');
const utils = require('./build_utils');

module.exports = {

axios: null,
clean_up_axios: false,
axios_version: "0.24.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of this hard-coded version...

  • We cannot tell if any CVE or something happens to that package at that version unless manually checking the code, which could be a concern
  • If the lib was existed, and it's not this version, we will not know.

But, I cannot think of a better way😞.
Does any other nodejs lib using the similar approach to fetch dependency dynamically? How do they deal with the version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, I would need to look around and see.

We could put it in our dev dependencies and then pull it out of the package.json that way. Then it would get automatically code scanned by Dependabot.

Though the dependencies here are completely isolated from the rest of the node modules by being in the node_modules folder of the scripts/build_dependencies folder. So even if there was a vulnerability, it would only be for building the source and not the output or anything a customer/consumer of the CRT would see.
Not saying it is something we shouldn't fix, but the security impact is minimal because it just gets downloaded and used only for a build.

Copy link
Contributor

@TingDaoK TingDaoK Jul 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not totally against it. I think we need to do more research, or how do JS experts think about this? If they think it's okay, just ignore me by all means.🫣

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some looking, and it looks like the general consensus is "don't do it" based on other Javascript packages and such. I got around this by putting it in the devDependencies and then loading the package.json and getting the version number from there. Now we have the advantage of getting Dependabot code scanning, can easily change the version in a single place, and it is more in line with what I have seen in other Javascript packages/modules.


/**
* Loads the axios library. We want to do this seperate instead of having a performStep function
* because the axios library is needed for multiple functions that have different data passed to them.
*/
loadAxios: function () {
const workDir = path.join(__dirname, "../../")

process.chdir(__dirname);
if (this.axios == null) {
if (utils.npmCheckIfPackageExists("axios", this.axios_version) == true) {
this.axios = require("axios");
} else {
try {
this.clean_up_axios = utils.npmDownloadAndInstallRuntimePackage("axios", this.axios_version);
this.axios = require('axios');
} catch (error) {
console.log("ERROR: Could not download axios! Cannot build CRT");
console.log("Please install axios verion " + this.axios_version + " and then run the aws-crt install script again");
process.exit(1);
}
}
}
process.chdir(workDir);
},

/**
* Downloads the file from the given file URL and places it in the given output location path.
* @param {*} fileUrl The file to download
* @param {*} outputLocationPath The location to store the downloaded file
* @returns A promise for the file download
*/
downloadFile: function (fileUrl, outputLocationPath) {
const writer = fs.createWriteStream(outputLocationPath);
return this.axios({
method: 'get',
url: fileUrl,
responseType: 'stream',
}).then(response => {
return new Promise((resolve, reject) => {
response.data.pipe(writer);
let error = null;
writer.on('error', err => {
error = err;
writer.close();
reject(err);
});
writer.on('close', () => {
if (!error) {
resolve();
}
});
});
});
},

/**
* Performs a checksum check on the given file. The checksum is downloaded from the given URL
* and then the file given is checked using said checksum.
* @param {*} url The URL containing the checksum
* @param {*} local_file The file to check
* @returns A promise for the result of the check
*/
checkChecksum: function (url, local_file) {
return this.axios({
method: 'get',
url: url,
responseType: 'text',
}).then(response => {
return new Promise((resolve, reject) => {
const filestream = fs.createReadStream(local_file);
const hash = crypto.createHash('sha256');
filestream.on('readable', () => {
// Only one element is going to be produced by the
// hash stream.
const data = filestream.read();
if (data)
hash.update(data);
else {
const checksum = hash.digest("hex")
if (checksum === response.data) {
resolve()
}
else {
reject(new Error("source code checksum mismatch"))
}
}
});
});
})
}

}
Loading