Skip to content

Commit

Permalink
cdxgen secure image - WIP (#1600)
Browse files Browse the repository at this point in the history
Secure mode WIP

Signed-off-by: Prabhu Subramanian <[email protected]>
  • Loading branch information
prabhu authored Jan 26, 2025
1 parent e2613ad commit 2d07991
Show file tree
Hide file tree
Showing 20 changed files with 620 additions and 242 deletions.
87 changes: 66 additions & 21 deletions .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,69 @@ jobs:
cdxgen-oci-image.cdx.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
containers-perms:
if: github.repository == 'CycloneDX/cdxgen'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '23.x'
registry-url: https://registry.npmjs.org/
- uses: oras-project/setup-oras@v1
- name: Trim CI agent
run: |
chmod +x contrib/free_disk_space.sh
./contrib/free_disk_space.sh
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta2
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/cyclonedx/cdxgen-secure
- name: Build and push Docker images
uses: docker/build-push-action@v5
with:
context: .
file: ci/Dockerfile-secure
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta2.outputs.tags }}
labels: ${{ steps.meta2.outputs.labels }}
cache-from: type=gha,scope=cdxgen-secure
cache-to: type=gha,mode=max,scope=cdxgen-secure
- name: Attach cdx sbom
run: |
corepack enable
corepack pnpm install
node bin/cdxgen.js -t docker -o cdxgen-secure-oci-image.cdx.json ghcr.io/cyclonedx/cdxgen-secure:latest
oras attach --artifact-type sbom/cyclonedx ghcr.io/cyclonedx/cdxgen-secure:latest ./cdxgen-secure-oci-image.cdx.json:application/json
oras discover --format tree ghcr.io/cyclonedx/cdxgen-secure:latest
continue-on-error: true
if: startsWith(github.ref, 'refs/tags/')
- name: Attach cdx secure sbom to release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
cdxgen-secure-oci-image.cdx.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
containers-deno:
if: github.repository == 'CycloneDX/cdxgen'
runs-on: ubuntu-latest
Expand All @@ -140,17 +203,11 @@ jobs:
with:
node-version: '23.x'
registry-url: https://registry.npmjs.org/
- uses: oras-project/setup-oras@v1
- name: Trim CI agent
run: |
chmod +x contrib/free_disk_space.sh
./contrib/free_disk_space.sh
- name: Setup nydus
run: |
curl -LO https://github.com/dragonflyoss/nydus/releases/download/v2.2.4/nydus-static-v2.2.4-linux-amd64.tgz
tar -xvf nydus-static-v2.2.4-linux-amd64.tgz
chmod +x nydus-static/*
mv nydus-static/* /usr/local/bin/
rm -rf nydus-static-v2.2.4-linux-amd64.tgz nydus-static
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
Expand Down Expand Up @@ -192,17 +249,11 @@ jobs:
with:
node-version: '23.x'
registry-url: https://registry.npmjs.org/
- uses: oras-project/setup-oras@v1
- name: Trim CI agent
run: |
chmod +x contrib/free_disk_space.sh
./contrib/free_disk_space.sh
- name: Setup nydus
run: |
curl -LO https://github.com/dragonflyoss/nydus/releases/download/v2.2.4/nydus-static-v2.2.4-linux-amd64.tgz
tar -xvf nydus-static-v2.2.4-linux-amd64.tgz
chmod +x nydus-static/*
mv nydus-static/* /usr/local/bin/
rm -rf nydus-static-v2.2.4-linux-amd64.tgz nydus-static
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
Expand Down Expand Up @@ -244,17 +295,11 @@ jobs:
with:
node-version: '23.x'
registry-url: https://registry.npmjs.org/
- uses: oras-project/setup-oras@v1
- name: Trim CI agent
run: |
chmod +x contrib/free_disk_space.sh
./contrib/free_disk_space.sh
- name: Setup nydus
run: |
curl -LO https://github.com/dragonflyoss/nydus/releases/download/v2.2.4/nydus-static-v2.2.4-linux-amd64.tgz
tar -xvf nydus-static-v2.2.4-linux-amd64.tgz
chmod +x nydus-static/*
mv nydus-static/* /usr/local/bin/
rm -rf nydus-static-v2.2.4-linux-amd64.tgz nydus-static
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/repotests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
run: |
chmod +x contrib/free_disk_space.sh
./contrib/free_disk_space.sh
- uses: sbt/setup-sbt@v1
- name: Install bazelisk - linux
if: matrix.os == 'ubuntu-latest'
run: |
Expand Down
132 changes: 119 additions & 13 deletions bin/cdxgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ import {
printSummary,
printTable,
} from "../lib/helpers/display.js";
import { ATOM_DB, dirNameStr, getTmpDir } from "../lib/helpers/utils.js";
import {
ATOM_DB,
dirNameStr,
getTmpDir,
isMac,
isSecureMode,
isWin,
} from "../lib/helpers/utils.js";
import { validateBom } from "../lib/helpers/validator.js";
import { postProcess } from "../lib/stages/postgen/postgen.js";
import { prepareEnv } from "../lib/stages/pregen/pregen.js";
Expand Down Expand Up @@ -141,6 +148,7 @@ const args = yargs(hideBin(process.argv))
})
.option("fail-on-error", {
type: "boolean",
default: !isSecureMode,
description: "Fail if any dependency extractor fails.",
})
.option("no-babel", {
Expand All @@ -167,7 +175,7 @@ const args = yargs(hideBin(process.argv))
})
.option("install-deps", {
type: "boolean",
default: true,
default: !isSecureMode,
description:
"Install dependencies automatically for some projects. Defaults to true but disabled for containers and oci scans. Use --no-install-deps to disable this feature.",
})
Expand Down Expand Up @@ -357,6 +365,10 @@ const args = yargs(hideBin(process.argv))
.alias("h", "help")
.wrap(Math.min(120, yargs().terminalWidth())).argv;

if (process.env?.CDXGEN_NODE_OPTIONS) {
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ""} ${process.env.CDXGEN_NODE_OPTIONS}`;
}

if (args.version) {
const packageJsonAsString = fs.readFileSync(
join(dirName, "..", "package.json"),
Expand All @@ -376,7 +388,7 @@ if (process.env.GLOBAL_AGENT_HTTP_PROXY || process.env.HTTP_PROXY) {
globalAgent.bootstrap();
}

const filePath = args._[0] || ".";
const filePath = args._[0] || process.cwd();
if (!args.projectName) {
if (filePath !== ".") {
args.projectName = basename(filePath);
Expand Down Expand Up @@ -460,15 +472,15 @@ const applyAdvancedOptions = (options) => {
options.deep = true;
options.evidence = false;
options.includeCrypto = false;
options.installDeps = true;
options.installDeps = !isSecureMode;
break;
case "deep-learning":
case "ml-deep":
process.env.FETCH_LICENSE = "true";
options.deep = true;
options.evidence = true;
options.includeCrypto = true;
options.installDeps = true;
options.installDeps = !isSecureMode;
break;
default:
break;
Expand Down Expand Up @@ -526,31 +538,122 @@ applyAdvancedOptions(options);
* Check for node >= 20 permissions
*
* @param {string} filePath File path
* @param {Object} options CLI Options
* @returns
*/
const checkPermissions = (filePath) => {
const checkPermissions = (filePath, options) => {
if (!process.permission) {
return true;
}
const fullFilePath = resolve(filePath);
// Secure mode checks
if (isSecureMode) {
if (process.permission.has("fs.read", "*")) {
console.log(
"\x1b[1;35mSECURE MODE: DO NOT run cdxgen with FileSystemRead permission set to wildcard.\x1b[0m",
);
}
if (process.permission.has("fs.write", "*")) {
console.log(
"\x1b[1;35mSECURE MODE: DO NOT run cdxgen with FileSystemWrite permission set to wildcard.\x1b[0m",
);
}
if (process.permission.has("worker")) {
console.log(
"SECURE MODE: DO NOT run cdxgen with worker thread permission! Remove `--allow-worker` argument.",
);
}
if (filePath !== fullFilePath) {
console.log(
`\x1b[1;35mSECURE MODE: Invoke cdxgen with an absolute path to improve security. Use ${fullFilePath} instead of ${filePath}.\x1b[0m`,
);
if (fullFilePath.includes(" ")) {
console.log(
"\x1b[1;35mSECURE MODE: Directory names containing spaces are known to cause issues. Rename the directories by replacing spaces with hyphens or underscores.\x1b[0m",
);
} else if (fullFilePath.length > 255 && isWin) {
console.log(
"Ensure 'Enable Win32 Long paths' is set to 'Enabled' by using Group Policy Editor.",
);
}
return false;
}
}

if (!process.permission.has("fs.read", filePath)) {
console.log(
`FileSystemRead permission required. Please invoke with the argument --allow-fs-read="${resolve(
`\x1b[1;35mSECURE MODE: FileSystemRead permission required. Please invoke cdxgen with the argument --allow-fs-read="${resolve(
filePath,
)}"`,
)}"\x1b[0m`,
);
return false;
}
if (!process.permission.has("fs.write", options.output)) {
console.log(
`\x1b[1;35mSECURE MODE: FileSystemWrite permission is required to create the output BOM file. Please invoke cdxgen with the argument --allow-fs-write="${options.output}"\x1b[0m`,
);
}
if (options.evidence) {
const slicesFilesKeys = [
"deps-slices-file",
"usages-slices-file",
"reachables-slices-file",
];
if (options?.type?.includes("swift")) {
slicesFilesKeys.push("semantics-slices-file");
}
for (const sf of slicesFilesKeys) {
let issueFound = false;
if (!process.permission.has("fs.write", options[sf])) {
console.log(
`SECURE MODE: FileSystemWrite permission is required to create the output slices file. Please invoke cdxgen with the argument --allow-fs-write="${options[sf]}"`,
);
if (!issueFound) {
issueFound = true;
}
}
if (issueFound) {
return false;
}
}
if (!process.permission.has("fs.write", process.env.ATOM_DB || ATOM_DB)) {
console.log(
`SECURE MODE: FileSystemWrite permission is required to create the output slices file. Please invoke cdxgen with the argument --allow-fs-write="${process.env.ATOM_DB || ATOM_DB}"`,
);
return false;
}
console.log(
"TIP: Invoke cdxgen with `--allow-addons` to allow the use of sqlite3 native addon. This addon is required for evidence mode.",
);
} else {
if (process.permission.has("fs.write", process.env.ATOM_DB || ATOM_DB)) {
console.log(
`SECURE MODE: FileSystemWrite permission is not required for the directory "${process.env.ATOM_DB || ATOM_DB}" in non-evidence mode. Consider removing the argument --allow-fs-write="${process.env.ATOM_DB || ATOM_DB}".`,
);
return false;
}
}
if (!process.permission.has("fs.write", getTmpDir())) {
console.log(
`FileSystemWrite permission required. Please invoke with the argument --allow-fs-write="${getTmpDir()}"`,
`FileSystemWrite permission may be required to the TEMP directory. Please invoke cdxgen with the argument --allow-fs-write="${join(getTmpDir(), "*")}"`,
);
return false;
if (isMac) {
console.log(
"TIP: macOS doesn't use `/tmp` prefix for TEMP directories. Use the argument shown above.",
);
}
}
if (!process.permission.has("child")) {
if (!process.permission.has("child") && !isSecureMode) {
console.log(
"ChildProcess permission is missing. This is required to spawn commands for some languages. Please invoke with the argument --allow-child-process",
"ChildProcess permission is missing. This is required to spawn commands for some languages. Please invoke cdxgen with the argument --allow-child-process in case of issues.",
);
}
if (process.permission.has("child") && options?.lifecycle === "pre-build") {
console.log(
"SECURE MODE: ChildProcess permission is not required for pre-build SBOM generation. Please invoke cdxgen without the argument --allow-child-process.",
);
return false;
}
return true;
};

Expand All @@ -566,7 +669,10 @@ const checkPermissions = (filePath) => {
return serverModule.start(options);
}
// Check if cdxgen has the required permissions
if (!checkPermissions(filePath)) {
if (!checkPermissions(filePath, options)) {
if (isSecureMode) {
process.exit(1);
}
return;
}
// This will prevent people from accidentally using the usages slices belonging to a different project
Expand Down
4 changes: 4 additions & 0 deletions bin/evinse.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ const evinseArt = `
╚══════╝ ╚═══╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝
`;

if (process.env?.CDXGEN_NODE_OPTIONS) {
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ""} ${process.env.CDXGEN_NODE_OPTIONS}`;
}

console.log(evinseArt);
(async () => {
// First, prepare the database by cataloging jars and other libraries
Expand Down
4 changes: 4 additions & 0 deletions bin/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const cdxArt = `

console.log(cdxArt);

if (process.env?.CDXGEN_NODE_OPTIONS) {
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ""} ${process.env.CDXGEN_NODE_OPTIONS}`;
}

// The current sbom is stored here
let sbom = undefined;

Expand Down
4 changes: 4 additions & 0 deletions bin/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ if (args.version) {
process.exit(0);
}

if (process.env?.CDXGEN_NODE_OPTIONS) {
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS || ""} ${process.env.CDXGEN_NODE_OPTIONS}`;
}

const bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
let hasInvalidComp = false;
// Validate any component signature
Expand Down
Loading

0 comments on commit 2d07991

Please sign in to comment.