From 6254a4c5d5855399e5ae4d1f5e86610711eb2d2d Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Tue, 21 May 2024 14:22:48 -0600 Subject: [PATCH] Create codeql.yml --- .github/workflows/codeql.yml | 95 ++++++++++++++++++++++++++ build.mjs | 125 +++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 .github/workflows/codeql.yml create mode 100644 build.mjs diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..472a29e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,95 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '15 14 * * 6' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: 'c-cpp' + build-mode: 'manual' + sourceDirectory: './addon' + - language: 'javascript-typescript' + build-mode: 'none' + sourceDirectory: "./src" + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + source-root: ${{ matrix.sourceDirectory }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + npm install + node ./github/workflows/libmongocrypt.mjs + npm run prebuild + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/build.mjs b/build.mjs new file mode 100644 index 0000000..aea1f78 --- /dev/null +++ b/build.mjs @@ -0,0 +1,125 @@ +import util from 'node:util'; +import process from 'node:process'; +import fs from 'node:fs/promises'; +import child_process from 'node:child_process'; +import events from 'node:events'; +import path from 'node:path'; + +async function parseArguments() { + const jsonImport = { [process.version.split('.').at(0) === 'v16' ? 'assert' : 'with']: { type: 'json' } }; + const pkg = (await import('./package.json', jsonImport)).default; + const libmongocryptVersion = pkg['mongodb:libmongocrypt']; + + const options = { + url: { short: 'u', type: 'string', default: 'https://github.com/mongodb/libmongocrypt.git' }, + libversion: { short: 'l', type: 'string', default: libmongocryptVersion }, + clean: { short: 'c', type: 'boolean' }, + help: { short: 'h', type: 'boolean' } + }; + + const args = util.parseArgs({ args: process.argv.slice(2), options, allowPositionals: false }); + + if (args.values.help) { + console.log( + `${process.argv[1]} ${[...Object.keys(options)] + .filter(k => k !== 'help') + .map(k => `[--${k}=${options[k].type}]`) + .join(' ')}` + ); + process.exit(0); + } + + return { + libmongocrypt: { url: args.values.url, ref: args.values.libversion }, + clean: args.values.clean + }; +} + +/** `xtrace` style command runner, uses spawn so that stdio is inherited */ +async function run(command, args = [], options = {}) { + console.error(`+ ${command} ${args.join(' ')}`, options.cwd ? `(in: ${options.cwd})` : ''); + await events.once(child_process.spawn(command, args, { stdio: 'inherit', ...options }), 'exit'); +} + +/** CLI flag maker: `toFlags({a: 1, b: 2})` yields `['-a=1', '-b=2']` */ +function toFlags(object) { + return Array.from(Object.entries(object)).map(([k, v]) => `-${k}=${v}`); +} + +const args = await parseArguments(); +const libmongocryptRoot = path.resolve('_libmongocrypt'); + +const currentLibMongoCryptBranch = await fs.readFile(path.join(libmongocryptRoot, '.git', 'HEAD'), 'utf8').catch(() => '') +const libmongocryptAlreadyClonedAndCheckedOut = currentLibMongoCryptBranch.trim().endsWith(`r-${args.libmongocrypt.ref}`); + +if (!args.clean && !libmongocryptAlreadyClonedAndCheckedOut) { + console.error('fetching libmongocrypt...', args.libmongocrypt); + await fs.rm(libmongocryptRoot, { recursive: true, force: true }); + await run('git', ['clone', args.libmongocrypt.url, libmongocryptRoot]); + await run('git', ['fetch', '--tags'], { cwd: libmongocryptRoot }); + await run('git', ['checkout', args.libmongocrypt.ref, '-b', `r-${args.libmongocrypt.ref}`], { cwd: libmongocryptRoot }); +} else { + console.error('libmongocrypt already up to date...', args.libmongocrypt); +} + +const libmongocryptBuiltVersion = await fs.readFile(path.join(libmongocryptRoot, 'VERSION_CURRENT'), 'utf8').catch(() => ''); +const libmongocryptAlreadyBuilt = libmongocryptBuiltVersion.trim() === args.libmongocrypt.ref; + +if (!args.clean && !libmongocryptAlreadyBuilt) { + console.error('building libmongocrypt...\n', args); + + const nodeDepsRoot = path.resolve('deps'); + const nodeBuildRoot = path.resolve(nodeDepsRoot, 'tmp', 'libmongocrypt-build'); + + await fs.rm(nodeBuildRoot, { recursive: true, force: true }); + await fs.mkdir(nodeBuildRoot, { recursive: true }); + + const CMAKE_FLAGS = toFlags({ + /** + * We provide crypto hooks from Node.js binding to openssl (so disable system crypto) + * TODO: NODE-5455 + * + * One thing that is not obvious from the build instructions for libmongocrypt + * and the Node.js bindings is that the Node.js driver uses libmongocrypt in + * DISABLE_NATIVE_CRYPTO aka nocrypto mode, that is, instead of using native + * system libraries for crypto operations, it provides callbacks to libmongocrypt + * which, in the Node.js addon case, call JS functions that in turn call built-in + * Node.js crypto methods. + * + * That’s way more convoluted than it needs to be, considering that we always + * have a copy of OpenSSL available directly, but for now it seems to make sense + * to stick with what the Node.js addon does here. + */ + DDISABLE_NATIVE_CRYPTO: '1', + /** A consistent name for the output "library" directory */ + DCMAKE_INSTALL_LIBDIR: 'lib', + /** No warnings allowed */ + DENABLE_MORE_WARNINGS_AS_ERRORS: 'ON', + /** Where to build libmongocrypt */ + DCMAKE_PREFIX_PATH: nodeDepsRoot, + /** + * Where to install libmongocrypt + * Note that `binding.gyp` will set `./deps/include` + * as an include path if BUILD_TYPE=static + */ + DCMAKE_INSTALL_PREFIX: nodeDepsRoot + }); + + const WINDOWS_CMAKE_FLAGS = + process.platform === 'win32' // Windows is still called "win32" when it is 64-bit + ? toFlags({ Thost: 'x64', A: 'x64', DENABLE_WINDOWS_STATIC_RUNTIME: 'ON' }) + : []; + + const MACOS_CMAKE_FLAGS = + process.platform === 'darwin' // The minimum macos target version we want for + ? toFlags({ DCMAKE_OSX_DEPLOYMENT_TARGET: '10.12' }) + : []; + + await run('cmake', [...CMAKE_FLAGS, ...WINDOWS_CMAKE_FLAGS, ...MACOS_CMAKE_FLAGS, libmongocryptRoot], { cwd: nodeBuildRoot }); + await run('cmake', ['--build', '.', '--target', 'install', '--config', 'RelWithDebInfo'], { cwd: nodeBuildRoot }); +} else { + console.error('libmongocrypt already built...'); +} + +await run('npm', ['install', '--ignore-scripts']); +await run('npm', ['run', 'rebuild'], { env: { ...process.env, BUILD_TYPE: 'static' } }); \ No newline at end of file