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

Evinse for Ruby #1557

Merged
merged 2 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
77 changes: 77 additions & 0 deletions .github/workflows/build-base-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,83 @@ jobs:
tags: ${{ steps.meta-debian-ruby18.outputs.tags }}
labels: ${{ steps.meta-debian-ruby18.outputs.labels }}

debian-ruby26-image:
if: github.repository == 'CycloneDX/cdxgen'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- 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: meta-debian-ruby26
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/cyclonedx/debian-ruby26

- name: Build and push Docker images
uses: docker/build-push-action@v5
with:
context: .
file: ci/base-images/debian/Dockerfile.ruby26
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta-debian-ruby26.outputs.tags }}
labels: ${{ steps.meta-debian-ruby26.outputs.labels }}

cdxgen-debian-ruby26-image:
if: github.repository == 'CycloneDX/cdxgen'
runs-on: ubuntu-latest
needs: debian-ruby26-image
permissions:
packages: write
steps:
- uses: actions/checkout@v4

- 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: meta-cdxgen-debian-ruby26
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/cyclonedx/cdxgen-debian-ruby26

- name: Build and push Docker images
uses: docker/build-push-action@v5
if: github.ref == 'refs/heads/master'
with:
context: .
file: ci/base-images/cdxgen/debian/Dockerfile.ruby26
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/cyclonedx/cdxgen-debian-ruby26:v11
labels: ${{ steps.meta-cdxgen-debian-ruby26.outputs.labels }}

sle-dotnet7-image:
if: github.repository == 'CycloneDX/cdxgen'
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions bin/evinse.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const args = yargs(hideBin(process.argv))
"php",
"swift",
"ios",
"ruby",
],
})
.option("db-path", {
Expand Down
31 changes: 31 additions & 0 deletions ci/base-images/cdxgen/debian/Dockerfile.ruby26
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM ghcr.io/cyclonedx/debian-ruby26:master

LABEL maintainer="CycloneDX" \
org.opencontainers.image.authors="Team AppThreat <[email protected]>" \
org.opencontainers.image.source="https://github.com/CycloneDX/cdxgen" \
org.opencontainers.image.url="https://github.com/CycloneDX/cdxgen" \
org.opencontainers.image.version="rolling" \
org.opencontainers.image.vendor="AppThreat" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.title="cdxgen" \
org.opencontainers.image.description="Rolling image with cdxgen SBOM generator for Ruby 2.6 apps" \
org.opencontainers.docker.cmd="docker run --rm -v /tmp:/tmp -p 9090:9090 -v $(pwd):/app:rw -t ghcr.io/cyclonedx/cdxgen-debian-ruby26:v11 -r /app --server"

ENV CDXGEN_IN_CONTAINER=true \
NODE_COMPILE_CACHE="/opt/cdxgen-node-cache" \
CDXGEN_GEM_HOME="/tmp/gems" \
ATOM_RUBY_HOME=/root/.rbenv/versions/3.4.1 \
RUBY_CMD=/root/.rbenv/versions/3.4.1/bin/ruby \
PYTHONPATH=/opt/pypi
ENV PATH=${PATH}:/usr/local/bin:/opt/pypi/bin:/opt/cdxgen/node_modules/.bin:

COPY . /opt/cdxgen

RUN cd /opt/cdxgen && corepack enable && corepack pnpm install --prod --package-import-method copy && corepack pnpm cache delete \
&& mkdir -p /opt/cdxgen-node-cache \
&& node /opt/cdxgen/bin/cdxgen.js --help \
&& rbastgen --help \
&& rm -rf ${CDXGEN_GEM_HOME} && mkdir -p ${CDXGEN_GEM_HOME} \
&& chmod a-w -R /opt

ENTRYPOINT ["node", "/opt/cdxgen/bin/cdxgen.js"]
1 change: 1 addition & 0 deletions ci/base-images/debian/Dockerfile.ruby18
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ RUN set -ex \
libncurses5-dev libsqlite3-dev libtool libyaml-dev pkg-config sqlite3 zlib1g-dev libgmp-dev libreadline6-dev libssl-dev libc-dev libxslt-dev libmagickwand-dev \
&& command curl -sSL https://rvm.io/mpapis.asc | gpg2 --import - \
&& command curl -sSL https://rvm.io/pkuczynski.asc | gpg2 --import - \
&& locale-gen en_US.UTF-8 \
&& echo "export rvm_max_time_flag=20" >> ~/.rvmrc \
&& curl -sSL https://get.rvm.io | bash -s stable --ruby=${RUBY_VERSION} \
&& rvm use ruby-${RUBY_VERSION} \
Expand Down
32 changes: 32 additions & 0 deletions ci/base-images/debian/Dockerfile.ruby26
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM ruby:2.6.10

ARG JAVA_VERSION=23.0.1-tem
ARG NODE_VERSION=20.18.1
ARG ATOM_RUBY_VERSION=3.4.1

ENV JAVA_VERSION=$JAVA_VERSION \
JAVA_HOME="/opt/java/${JAVA_VERSION}" \
ATOM_RUBY_VERSION=$ATOM_RUBY_VERSION \
BUNDLE_SILENCE_ROOT_WARNING=true \
LC_ALL=en_US.UTF-8 \
LANG=en_US.UTF-8 \
LANGUAGE=en_US.UTF-8 \
NVM_DIR="/root/.nvm"
ENV PATH=/root/.nvm/versions/node/v${NODE_VERSION}/bin:${PATH}:/usr/local/bin:/root/.local/bin:/root/.rbenv/bin:

COPY ci/base-images/debian/install.sh /tmp/

RUN apt-get update && apt-get install -qq -y --no-install-recommends curl bash bzip2 git-core zip unzip make gawk \
&& apt-get install -qq -y build-essential gcc-9 g++-9 python2 libmagic-dev locales nodejs \
&& locale-gen en_US.UTF-8 \
&& gem install bundler -v 1.17.3 \
&& bundle config git.allow_insecure true \
&& chmod +x /tmp/install.sh \
&& SKIP_PYTHON=yes ./tmp/install.sh && rm /tmp/install.sh \
&& node -v \
&& npm -v \
&& npm install -g corepack \
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/*

CMD /bin/bash
1 change: 1 addition & 0 deletions ci/base-images/debian/Dockerfile.ruby33
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ COPY ci/base-images/debian/install.sh /tmp/

RUN apt-get update && apt-get install -qq -y --no-install-recommends curl bash bzip2 git-core zip unzip make gawk \
&& apt-get install -qq -y build-essential python3 python3-pip python3-dev libmagic-dev locales \
&& locale-gen en_US.UTF-8 \
&& chmod +x /tmp/install.sh \
&& ./tmp/install.sh && rm /tmp/install.sh \
&& node -v \
Expand Down
1 change: 1 addition & 0 deletions ci/base-images/debian/Dockerfile.ruby34
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ COPY ci/base-images/debian/install.sh /tmp/

RUN apt-get update && apt-get install -qq -y --no-install-recommends curl bash bzip2 git-core zip unzip make gawk \
&& apt-get install -qq -y build-essential python3 python3-pip python3-dev libmagic-dev locales \
&& locale-gen en_US.UTF-8 \
&& chmod +x /tmp/install.sh \
&& ./tmp/install.sh && rm /tmp/install.sh \
&& node -v \
Expand Down
2 changes: 2 additions & 0 deletions ci/base-images/sle/Dockerfile.ruby25
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ RUN set -e; \
&& mkdir -p "$(rbenv root)/plugins" \
&& git clone https://github.com/rbenv/ruby-build.git --depth=1 "$(rbenv root)/plugins/ruby-build" \
&& rbenv install ${ATOM_RUBY_VERSION} \
&& ruby --version \
&& java --version \
&& zypper clean -a

CMD /bin/bash
62 changes: 54 additions & 8 deletions lib/evinser/evinser.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ export async function createSlice(
// Support for crypto slices aka CBOM
if (sliceType === "reachables" && options.includeCrypto) {
args.push("--include-crypto");
} else if (sliceType === "usages") {
args.push("--remove-atom");
}
args = args.concat([
"-l",
Expand Down Expand Up @@ -308,6 +310,9 @@ export function purlToLanguage(purl, filePath) {
case "composer":
language = "php";
break;
case "gem":
language = "ruby";
break;
case "generic":
language = "c";
}
Expand All @@ -321,7 +326,7 @@ export function initFromSbom(components, language) {
if (!comp || !comp.evidence) {
continue;
}
if (language === "php") {
if (["php", "ruby"].includes(language)) {
(comp.properties || [])
.filter((v) => v.name === "Namespaces")
.forEach((v) => {
Expand Down Expand Up @@ -591,19 +596,28 @@ export async function parseSliceUsages(
for (const atype of [
[ausage?.targetObj?.isExternal, ausage?.targetObj?.typeFullName],
[ausage?.targetObj?.isExternal, ausage?.targetObj?.resolvedMethod],
[ausage?.definedBy?.name?.includes("::"), ausage?.definedBy?.name],
[ausage?.definedBy?.isExternal, ausage?.definedBy?.typeFullName],
[ausage?.definedBy?.isExternal, ausage?.definedBy?.resolvedMethod],
...(ausage?.fields || []).map((f) => [f?.isExternal, f?.typeFullName]),
]) {
if (
!atype[0] &&
(!atype[1] || ["ANY", "(...)", "<empty>"].includes(atype[1]))
) {
continue;
}
if (
atype[0] !== false &&
!isFilterableType(language, userDefinedTypesMap, atype[1])
) {
if (!atype[1].includes("(") && !atype[1].includes(".py")) {
typesToLookup.add(simplifyType(atype[1]));
// Javascript calls can be resolved to a precise line number only from the call nodes
// Javascript and Ruby calls can be resolved to a precise line number only from the call nodes
if (
["javascript", "js", "ts", "typescript"].includes(language) &&
["javascript", "js", "ts", "typescript", "ruby"].includes(
language,
) &&
ausageLine
) {
if (atype[1].includes(":")) {
Expand All @@ -626,7 +640,10 @@ export async function parseSliceUsages(
.concat(ausage?.invokedCalls || [])
.concat(ausage?.argToCalls || [])
.concat(ausage?.procedures || [])) {
if (acall.resolvedMethod?.startsWith("@")) {
if (
acall.resolvedMethod?.startsWith("@") ||
acall?.callName?.includes("::")
) {
typesToLookup.add(acall.callName);
if (acall.lineNumber) {
addToOverrides(
Expand Down Expand Up @@ -713,7 +730,7 @@ export async function parseSliceUsages(
if (purlImportsMap && Object.keys(purlImportsMap).length) {
for (const apurl of Object.keys(purlImportsMap)) {
const apurlImports = purlImportsMap[apurl];
if (["php", "python"].includes(language)) {
if (["php", "python", "ruby"].includes(language)) {
for (const aimp of apurlImports) {
if (atype.startsWith(aimp)) {
if (!purlLocationMap[apurl]) {
Expand Down Expand Up @@ -971,7 +988,9 @@ export function detectServicesFromUsages(language, slice, servicesMap = {}) {
const definedBy = usage?.definedBy;
let endpoints = [];
let authenticated = undefined;
if (targetObj?.resolvedMethod) {
if (language === "ruby" && definedBy?.name?.includes("/")) {
endpoints = extractEndpoints(language, definedBy.name);
} else if (targetObj?.resolvedMethod) {
if (language !== "php") {
endpoints = extractEndpoints(language, targetObj?.resolvedMethod);
}
Expand Down Expand Up @@ -1029,7 +1048,7 @@ export function detectServicesFromUsages(language, slice, servicesMap = {}) {
*/
export function detectServicesFromUDT(language, userDefinedTypes, servicesMap) {
if (
["python", "py", "c", "cpp", "c++", "php"].includes(language) &&
["python", "py", "c", "cpp", "c++", "php", "ruby"].includes(language) &&
userDefinedTypes &&
userDefinedTypes.length
) {
Expand Down Expand Up @@ -1140,6 +1159,31 @@ export function extractEndpoints(language, code) {
);
}
break;
case "ruby":
case "rb": {
let urlPrefix = "";
let urlSuffix = "";
if (code.includes("namespace ")) {
urlPrefix = code.split("namespace ").pop().split(" ")[0];
}
for (const m of ["get", "post", "delete", "options", "put", "head"]) {
if (code.includes(`${m} `)) {
urlSuffix = code.split(`${m} `).pop().split(" ")[0];
}
}
if (code.includes("http") && code.includes('"')) {
endpoints = code.split('"').filter((s) => s.startsWith("http"));
}
if (urlPrefix !== "" || urlSuffix !== "") {
if (!endpoints) {
endpoints = [];
}
endpoints.push(
`${urlPrefix.replace(/['"]/g, "")}${urlSuffix.replace(/['"]/g, "")}`,
);
}
break;
}
default:
endpoints = (code.match(/['"](.*?)['"]/gi) || [])
.map((v) => v.replace(/["']/g, "").replace("\n", ""))
Expand Down Expand Up @@ -1295,7 +1339,7 @@ export function createEvinseFile(sliceArtefacts, options) {
console.log(evinseOutFile, "created successfully.");
} else {
console.log(
"Unable to identify component evidence for the input SBOM. Only java, javascript, python, swift, and php projects are supported by evinse.",
"Unable to identify component evidence for the input SBOM. Only java, javascript, python, swift, php, and ruby projects are supported by evinse.",
);
}
if (tempDir?.startsWith(getTmpDir())) {
Expand Down Expand Up @@ -1590,6 +1634,8 @@ export function getClassTypeFromSignature(language, typeFullName) {
.replace(".__init__", "");
} else if (["php"].includes(language)) {
typeFullName = typeFullName.split("->")[0].split("::")[0];
} else if (["ruby"].includes(language)) {
typeFullName = typeFullName.split("::")[0];
}
if (
typeFullName.startsWith("<unresolved") ||
Expand Down
22 changes: 18 additions & 4 deletions lib/helpers/envcontext.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ export function installRubyVersion(rubyVersion, filePath) {
process.env?.CDXGEN_IN_CONTAINER !== "true"
) {
console.log(
`Installing Ruby version ${rubyVersion} requires specific development libraries. Consider using the custom container image "ghcr.io/cyclonedx/cdxgen-ruby25:v11" instead.`,
`Installing Ruby version ${rubyVersion} requires specific development libraries. Consider using the custom container image "ghcr.io/cyclonedx/cdxgen-debian-ruby26:v11" instead.`,
);
console.log("The below install step is likely to fail.");
}
Expand Down Expand Up @@ -942,7 +942,10 @@ export function performBundleInstall(
if (result.error || result.status !== 0) {
let pythonWarningShown = false;
let rubyVersionWarningShown = false;
if (result?.stderr?.includes("requires python 2 to be installed")) {
if (
result?.stderr?.includes("requires python 2 to be installed") ||
result?.stdout?.includes("requires python 2 to be installed")
) {
pythonWarningShown = true;
console.log(
"A native module requires python 2 to be installed. Please install python 2.7.18 from https://www.python.org/downloads/release/python-2718/.",
Expand All @@ -962,7 +965,10 @@ export function performBundleInstall(
"Alternatively, ensure Gemfile.lock is present locally and invoke cdxgen with the argument `--lifecycle pre-build`.",
);
}
if (result?.stderr?.includes("Running `bundle update ")) {
if (
result?.stderr?.includes("Running `bundle update ") ||
result?.stdout?.includes("Running `bundle update ")
) {
console.log(
"Gemfile.lock appears to be outdated. Attempting automated update.",
);
Expand Down Expand Up @@ -1002,7 +1008,10 @@ export function performBundleInstall(
}
return result.status === 0;
}
if (result?.stderr?.includes("Your Ruby version is ")) {
if (
result?.stderr?.includes("Your Ruby version is ") ||
result?.stdout?.includes("Your Ruby version is ")
) {
console.log(
"This project requires a specific version of Ruby. The version requirements can be found in the error message below.",
);
Expand Down Expand Up @@ -1034,6 +1043,11 @@ export function performBundleInstall(
"NOTE: The generated SBOM would be incomplete with this workaround.",
);
}
if (result?.stderr?.includes("Target architecture x64 is only supported")) {
console.log(
"A gem native extension requires x64/amd64 architecture. Run the cdxgen container image with the argument '--platform=linux/amd64'.",
);
}
if (
!pythonWarningShown &&
(result?.stderr?.includes("Failed to build gem native extension") ||
Expand Down
Loading
Loading