From a1b761e1a57626a68d94436be3a73c1b0a159a4b Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Tue, 18 Jun 2024 17:11:25 +0200 Subject: [PATCH] feat: implement osv-ingester service Signed-off-by: Ruben Romero Montes --- .github/workflows/ci.yaml | 56 +++ .github/workflows/commitlint.config.js | 1 + .gitignore | 43 ++ .mvn/wrapper/.gitignore | 1 + .mvn/wrapper/MavenWrapperDownloader.java | 98 +++++ .mvn/wrapper/maven-wrapper.properties | 18 + .tekton/osv-ingester-pull-request.yaml | 397 +++++++++++++++++ .tekton/osv-ingester-push.yaml | 394 +++++++++++++++++ deploy/openshift/template.yaml | 104 +++++ deploy/osv-ingester.yaml | 37 ++ deploy/redis.yaml | 69 +++ devfile.yaml | 32 ++ mvnw | 308 +++++++++++++ mvnw.cmd | 205 +++++++++ pom.xml | 413 ++++++++++++++++++ src/main/docker/Dockerfile.jvm | 95 ++++ src/main/docker/Dockerfile.multi-stage | 35 ++ src/main/docker/Dockerfile.native | 27 ++ src/main/docker/Dockerfile.native-micro | 30 ++ .../osvingester/cli/GCSIngesterCommand.java | 43 ++ .../osvingester/cli/OsvIngesterCommand.java | 27 ++ .../osvingester/model/IngestionReport.java | 24 + .../osvingester/service/EcosystemLoader.java | 31 ++ .../service/VulnerabilityIngester.java | 116 +++++ .../service/googlecloudstorage/GCSLoader.java | 143 ++++++ src/main/resources/application.properties | 12 + 26 files changed, 2759 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/commitlint.config.js create mode 100644 .gitignore create mode 100644 .mvn/wrapper/.gitignore create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 .tekton/osv-ingester-pull-request.yaml create mode 100644 .tekton/osv-ingester-push.yaml create mode 100644 deploy/openshift/template.yaml create mode 100644 deploy/osv-ingester.yaml create mode 100644 deploy/redis.yaml create mode 100644 devfile.yaml create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/docker/Dockerfile.jvm create mode 100644 src/main/docker/Dockerfile.multi-stage create mode 100644 src/main/docker/Dockerfile.native create mode 100644 src/main/docker/Dockerfile.native-micro create mode 100644 src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/GCSIngesterCommand.java create mode 100644 src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/OsvIngesterCommand.java create mode 100644 src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/model/IngestionReport.java create mode 100644 src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/EcosystemLoader.java create mode 100644 src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/VulnerabilityIngester.java create mode 100644 src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/googlecloudstorage/GCSLoader.java create mode 100644 src/main/resources/application.properties diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c579fb3 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,56 @@ +name: CI +on: + push: + branches: + - main + tags: + - '*' + pull_request: + branches: + - '*' + workflow_dispatch: # allow this workflow to be called by other workflows + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + +jobs: + commitlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install commitlint + run: | + npm install conventional-changelog-conventionalcommits + npm install @commitlint/config-conventional + npm install commitlint@latest + - name: Validate current commit (last commit) with commitlint + if: github.event_name == 'push' + run: npx commitlint --config .github/workflows/commitlint.config.js --from HEAD~1 --to HEAD --verbose + - name: Validate PR commits with commitlint + if: github.event_name == 'pull_request' + run: npx commitlint --config .github/workflows/commitlint.config.js --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose + integration-tests: + runs-on: ubuntu-latest + name: Integration Tests + needs: commitlint + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup JDK + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '21' + cache: 'maven' + - name: Run integration tests + run: | + ./mvnw -B verify -Pnative + env: + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/commitlint.config.js b/.github/workflows/commitlint.config.js new file mode 100644 index 0000000..33a7b5c --- /dev/null +++ b/.github/workflows/commitlint.config.js @@ -0,0 +1 @@ +module.exports = { extends: ['@commitlint/config-conventional'] } \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c7863e --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +#Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties +.flattened-pom.xml + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/.mvn/wrapper/.gitignore b/.mvn/wrapper/.gitignore new file mode 100644 index 0000000..e72f5e8 --- /dev/null +++ b/.mvn/wrapper/.gitignore @@ -0,0 +1 @@ +maven-wrapper.jar diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..84d1e60 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public final class MavenWrapperDownloader +{ + private static final String WRAPPER_VERSION = "3.2.0"; + + private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); + + public static void main( String[] args ) + { + log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); + + if ( args.length != 2 ) + { + System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); + System.exit( 1 ); + } + + try + { + log( " - Downloader started" ); + final URL wrapperUrl = new URL( args[0] ); + final String jarPath = args[1].replace( "..", "" ); // Sanitize path + final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); + downloadFileFromURL( wrapperUrl, wrapperJarPath ); + log( "Done" ); + } + catch ( IOException e ) + { + System.err.println( "- Error downloading: " + e.getMessage() ); + if ( VERBOSE ) + { + e.printStackTrace(); + } + System.exit( 1 ); + } + } + + private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) + throws IOException + { + log( " - Downloading to: " + wrapperJarPath ); + if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) + { + final String username = System.getenv( "MVNW_USERNAME" ); + final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); + Authenticator.setDefault( new Authenticator() + { + @Override + protected PasswordAuthentication getPasswordAuthentication() + { + return new PasswordAuthentication( username, password ); + } + } ); + } + try ( InputStream inStream = wrapperUrl.openStream() ) + { + Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); + } + log( " - Downloader complete" ); + } + + private static void log( String msg ) + { + if ( VERBOSE ) + { + System.out.println( msg ); + } + } + +} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..70f4f50 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/.tekton/osv-ingester-pull-request.yaml b/.tekton/osv-ingester-pull-request.yaml new file mode 100644 index 0000000..9cabd79 --- /dev/null +++ b/.tekton/osv-ingester-pull-request.yaml @@ -0,0 +1,397 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + annotations: + build.appstudio.openshift.io/repo: https://github.com/RHEcosystemAppEng/osv-ingester?rev={{revision}} + build.appstudio.redhat.com/commit_sha: '{{revision}}' + build.appstudio.redhat.com/pull_request_number: '{{pull_request_number}}' + build.appstudio.redhat.com/target_branch: '{{target_branch}}' + pipelinesascode.tekton.dev/max-keep-runs: "3" + pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch + == "main" + creationTimestamp: null + labels: + appstudio.openshift.io/application: exhort + appstudio.openshift.io/component: osv-ingester + pipelines.appstudio.openshift.io/type: build + name: osv-ingester-on-pull-request + namespace: trusted-content-tenant +spec: + params: + - name: dockerfile + value: src/main/docker/Dockerfile.multi-stage + - name: git-url + value: '{{source_url}}' + - name: image-expires-after + value: 5d + - name: output-image + value: quay.io/redhat-user-workloads/trusted-content-tenant/exhort/osv-ingester:on-pr-{{revision}} + - name: path-context + value: . + - name: revision + value: '{{revision}}' + pipelineSpec: + finally: + - name: show-sbom + params: + - name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + taskRef: + params: + - name: name + value: show-sbom + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-show-sbom:0.1@sha256:1f90faefa39c2e4965793c1d8321e7d5d99a6c941276a9094a4e0d483a598fca + - name: kind + value: task + resolver: bundles + - name: show-summary + params: + - name: pipelinerun-name + value: $(context.pipelineRun.name) + - name: git-url + value: $(tasks.clone-repository.results.url)?rev=$(tasks.clone-repository.results.commit) + - name: image-url + value: $(params.output-image) + - name: build-task-status + value: $(tasks.build-container.status) + taskRef: + params: + - name: name + value: summary + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-summary:0.2@sha256:bdf58a8a6bf10482fff841ce6c78c54e87d306bc6aae9515821c436d26daff35 + - name: kind + value: task + resolver: bundles + workspaces: + - name: workspace + workspace: workspace + params: + - description: Source Repository URL + name: git-url + type: string + - default: "" + description: Revision of the Source Repository + name: revision + type: string + - description: Fully Qualified Output Image + name: output-image + type: string + - default: . + description: Path to the source code of an application's component from where + to build image. + name: path-context + type: string + - default: Dockerfile + description: Path to the Dockerfile inside the context specified by parameter + path-context + name: dockerfile + type: string + - default: "false" + description: Force rebuild image + name: rebuild + type: string + - default: "false" + description: Skip checks against built image + name: skip-checks + type: string + - default: "false" + description: Execute the build with network isolation + name: hermetic + type: string + - default: "" + description: Build dependencies to be prefetched by Cachi2 + name: prefetch-input + type: string + - default: "false" + description: Java build + name: java + type: string + - default: "" + description: Image tag expiration time, time values could be something like + 1h, 2d, 3w for hours, days, and weeks, respectively. + name: image-expires-after + - default: "false" + description: Build a source image. + name: build-source-image + type: string + results: + - description: "" + name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + - description: "" + name: IMAGE_DIGEST + value: $(tasks.build-container.results.IMAGE_DIGEST) + - description: "" + name: CHAINS-GIT_URL + value: $(tasks.clone-repository.results.url) + - description: "" + name: CHAINS-GIT_COMMIT + value: $(tasks.clone-repository.results.commit) + - description: "" + name: JAVA_COMMUNITY_DEPENDENCIES + value: $(tasks.build-container.results.JAVA_COMMUNITY_DEPENDENCIES) + tasks: + - name: init + params: + - name: image-url + value: $(params.output-image) + - name: rebuild + value: $(params.rebuild) + - name: skip-checks + value: $(params.skip-checks) + taskRef: + params: + - name: name + value: init + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-init:0.2@sha256:686109bd8088258f73211618824aee5d3cf9e370f65fa3e85d361790a54260ef + - name: kind + value: task + resolver: bundles + - name: clone-repository + params: + - name: url + value: $(params.git-url) + - name: revision + value: $(params.revision) + runAfter: + - init + taskRef: + params: + - name: name + value: git-clone + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-git-clone:0.1@sha256:30709df067659a407968154fd39e99763823d8ecfc6b5cd00a55b68818ec94ba + - name: kind + value: task + resolver: bundles + when: + - input: $(tasks.init.results.build) + operator: in + values: + - "true" + workspaces: + - name: output + workspace: workspace + - name: basic-auth + workspace: git-auth + - name: prefetch-dependencies + params: + - name: input + value: $(params.prefetch-input) + runAfter: + - clone-repository + taskRef: + params: + - name: name + value: prefetch-dependencies + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-prefetch-dependencies:0.1@sha256:c6fdbf404dc61bf8cf8bec5fc4d7fb15f37ba62f1684de0c68bfbad5723c0052 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.hermetic) + operator: in + values: + - "true" + workspaces: + - name: source + workspace: workspace + - name: build-container + params: + - name: IMAGE + value: $(params.output-image) + - name: DOCKERFILE + value: $(params.dockerfile) + - name: CONTEXT + value: $(params.path-context) + - name: HERMETIC + value: $(params.hermetic) + - name: PREFETCH_INPUT + value: $(params.prefetch-input) + - name: IMAGE_EXPIRES_AFTER + value: $(params.image-expires-after) + - name: COMMIT_SHA + value: $(tasks.clone-repository.results.commit) + runAfter: + - prefetch-dependencies + taskRef: + params: + - name: name + value: buildah-8gb + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-buildah-8gb:0.1@sha256:e3c5aecec9379b1aeb1f404f60669bb7c02ca1a809a9a6d602b8257e864fc98c + - name: kind + value: task + resolver: bundles + when: + - input: $(tasks.init.results.build) + operator: in + values: + - "true" + workspaces: + - name: source + workspace: workspace + - name: build-source-image + params: + - name: BINARY_IMAGE + value: $(params.output-image) + - name: BASE_IMAGES + value: $(tasks.build-container.results.BASE_IMAGES_DIGESTS) + runAfter: + - build-container + taskRef: + params: + - name: name + value: source-build + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-source-build:0.1@sha256:1f62eaf64a188fcf61f808ad78a15ebf9a8f7f51c644266ad195718b6a2dd372 + - name: kind + value: task + resolver: bundles + when: + - input: $(tasks.init.results.build) + operator: in + values: + - "true" + - input: $(params.build-source-image) + operator: in + values: + - "true" + workspaces: + - name: workspace + workspace: workspace + - name: deprecated-base-image-check + params: + - name: BASE_IMAGES_DIGESTS + value: $(tasks.build-container.results.BASE_IMAGES_DIGESTS) + - name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + - name: IMAGE_DIGEST + value: $(tasks.build-container.results.IMAGE_DIGEST) + runAfter: + - build-container + taskRef: + params: + - name: name + value: deprecated-image-check + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-deprecated-image-check:0.4@sha256:6b1b325de0af29b6e9a0696f4d2b669a1e6a046941726cc97c5e42785aad870c + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + - name: clair-scan + params: + - name: image-digest + value: $(tasks.build-container.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-container.results.IMAGE_URL) + runAfter: + - build-container + taskRef: + params: + - name: name + value: clair-scan + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-clair-scan:0.1@sha256:a6107f78e5fa9e087992f11d788701e4241d9875b153def796fb3bf257c3b7fd + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + - name: sast-snyk-check + runAfter: + - clone-repository + taskRef: + params: + - name: name + value: sast-snyk-check + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-sast-snyk-check:0.1@sha256:b3d2d07394ff983d5f2578c294cd8c4e9428fecc801495feeb929d932c10f740 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + workspaces: + - name: workspace + workspace: workspace + - name: clamav-scan + params: + - name: image-digest + value: $(tasks.build-container.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-container.results.IMAGE_URL) + runAfter: + - build-container + taskRef: + params: + - name: name + value: clamav-scan + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-clamav-scan:0.1@sha256:6ba32717bd837ca0d5714b518cc4530e1f1d5bef137df54c02b0c2151b9d217e + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + - name: sbom-json-check + params: + - name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + - name: IMAGE_DIGEST + value: $(tasks.build-container.results.IMAGE_DIGEST) + runAfter: + - build-container + taskRef: + params: + - name: name + value: sbom-json-check + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-sbom-json-check:0.1@sha256:dbd467a0507cff1981d3c98f683339feaab1b387c5b5fbf1ff957e9be2e27027 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + workspaces: + - name: workspace + - name: git-auth + optional: true + taskRunTemplate: {} + workspaces: + - name: workspace + volumeClaimTemplate: + metadata: + creationTimestamp: null + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + status: {} + - name: git-auth + secret: + secretName: '{{ git_auth_secret }}' +status: {} diff --git a/.tekton/osv-ingester-push.yaml b/.tekton/osv-ingester-push.yaml new file mode 100644 index 0000000..47134b0 --- /dev/null +++ b/.tekton/osv-ingester-push.yaml @@ -0,0 +1,394 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + annotations: + build.appstudio.openshift.io/repo: https://github.com/RHEcosystemAppEng/osv-ingester?rev={{revision}} + build.appstudio.redhat.com/commit_sha: '{{revision}}' + build.appstudio.redhat.com/target_branch: '{{target_branch}}' + pipelinesascode.tekton.dev/max-keep-runs: "3" + pipelinesascode.tekton.dev/on-cel-expression: event == "push" && target_branch + == "main" + creationTimestamp: null + labels: + appstudio.openshift.io/application: exhort + appstudio.openshift.io/component: osv-ingester + pipelines.appstudio.openshift.io/type: build + name: osv-ingester-on-push + namespace: trusted-content-tenant +spec: + params: + - name: dockerfile + value: src/main/docker/Dockerfile.multi-stage + - name: git-url + value: '{{source_url}}' + - name: output-image + value: quay.io/redhat-user-workloads/trusted-content-tenant/exhort/osv-ingester:{{revision}} + - name: path-context + value: . + - name: revision + value: '{{revision}}' + pipelineSpec: + finally: + - name: show-sbom + params: + - name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + taskRef: + params: + - name: name + value: show-sbom + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-show-sbom:0.1@sha256:1f90faefa39c2e4965793c1d8321e7d5d99a6c941276a9094a4e0d483a598fca + - name: kind + value: task + resolver: bundles + - name: show-summary + params: + - name: pipelinerun-name + value: $(context.pipelineRun.name) + - name: git-url + value: $(tasks.clone-repository.results.url)?rev=$(tasks.clone-repository.results.commit) + - name: image-url + value: $(params.output-image) + - name: build-task-status + value: $(tasks.build-container.status) + taskRef: + params: + - name: name + value: summary + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-summary:0.2@sha256:bdf58a8a6bf10482fff841ce6c78c54e87d306bc6aae9515821c436d26daff35 + - name: kind + value: task + resolver: bundles + workspaces: + - name: workspace + workspace: workspace + params: + - description: Source Repository URL + name: git-url + type: string + - default: "" + description: Revision of the Source Repository + name: revision + type: string + - description: Fully Qualified Output Image + name: output-image + type: string + - default: . + description: Path to the source code of an application's component from where + to build image. + name: path-context + type: string + - default: Dockerfile + description: Path to the Dockerfile inside the context specified by parameter + path-context + name: dockerfile + type: string + - default: "false" + description: Force rebuild image + name: rebuild + type: string + - default: "false" + description: Skip checks against built image + name: skip-checks + type: string + - default: "false" + description: Execute the build with network isolation + name: hermetic + type: string + - default: "" + description: Build dependencies to be prefetched by Cachi2 + name: prefetch-input + type: string + - default: "false" + description: Java build + name: java + type: string + - default: "" + description: Image tag expiration time, time values could be something like + 1h, 2d, 3w for hours, days, and weeks, respectively. + name: image-expires-after + - default: "false" + description: Build a source image. + name: build-source-image + type: string + results: + - description: "" + name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + - description: "" + name: IMAGE_DIGEST + value: $(tasks.build-container.results.IMAGE_DIGEST) + - description: "" + name: CHAINS-GIT_URL + value: $(tasks.clone-repository.results.url) + - description: "" + name: CHAINS-GIT_COMMIT + value: $(tasks.clone-repository.results.commit) + - description: "" + name: JAVA_COMMUNITY_DEPENDENCIES + value: $(tasks.build-container.results.JAVA_COMMUNITY_DEPENDENCIES) + tasks: + - name: init + params: + - name: image-url + value: $(params.output-image) + - name: rebuild + value: $(params.rebuild) + - name: skip-checks + value: $(params.skip-checks) + taskRef: + params: + - name: name + value: init + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-init:0.2@sha256:686109bd8088258f73211618824aee5d3cf9e370f65fa3e85d361790a54260ef + - name: kind + value: task + resolver: bundles + - name: clone-repository + params: + - name: url + value: $(params.git-url) + - name: revision + value: $(params.revision) + runAfter: + - init + taskRef: + params: + - name: name + value: git-clone + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-git-clone:0.1@sha256:30709df067659a407968154fd39e99763823d8ecfc6b5cd00a55b68818ec94ba + - name: kind + value: task + resolver: bundles + when: + - input: $(tasks.init.results.build) + operator: in + values: + - "true" + workspaces: + - name: output + workspace: workspace + - name: basic-auth + workspace: git-auth + - name: prefetch-dependencies + params: + - name: input + value: $(params.prefetch-input) + runAfter: + - clone-repository + taskRef: + params: + - name: name + value: prefetch-dependencies + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-prefetch-dependencies:0.1@sha256:c6fdbf404dc61bf8cf8bec5fc4d7fb15f37ba62f1684de0c68bfbad5723c0052 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.hermetic) + operator: in + values: + - "true" + workspaces: + - name: source + workspace: workspace + - name: build-container + params: + - name: IMAGE + value: $(params.output-image) + - name: DOCKERFILE + value: $(params.dockerfile) + - name: CONTEXT + value: $(params.path-context) + - name: HERMETIC + value: $(params.hermetic) + - name: PREFETCH_INPUT + value: $(params.prefetch-input) + - name: IMAGE_EXPIRES_AFTER + value: $(params.image-expires-after) + - name: COMMIT_SHA + value: $(tasks.clone-repository.results.commit) + runAfter: + - prefetch-dependencies + taskRef: + params: + - name: name + value: buildah-8gb + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-buildah-8gb:0.1@sha256:e3c5aecec9379b1aeb1f404f60669bb7c02ca1a809a9a6d602b8257e864fc98c + - name: kind + value: task + resolver: bundles + when: + - input: $(tasks.init.results.build) + operator: in + values: + - "true" + workspaces: + - name: source + workspace: workspace + - name: build-source-image + params: + - name: BINARY_IMAGE + value: $(params.output-image) + - name: BASE_IMAGES + value: $(tasks.build-container.results.BASE_IMAGES_DIGESTS) + runAfter: + - build-container + taskRef: + params: + - name: name + value: source-build + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-source-build:0.1@sha256:1f62eaf64a188fcf61f808ad78a15ebf9a8f7f51c644266ad195718b6a2dd372 + - name: kind + value: task + resolver: bundles + when: + - input: $(tasks.init.results.build) + operator: in + values: + - "true" + - input: $(params.build-source-image) + operator: in + values: + - "true" + workspaces: + - name: workspace + workspace: workspace + - name: deprecated-base-image-check + params: + - name: BASE_IMAGES_DIGESTS + value: $(tasks.build-container.results.BASE_IMAGES_DIGESTS) + - name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + - name: IMAGE_DIGEST + value: $(tasks.build-container.results.IMAGE_DIGEST) + runAfter: + - build-container + taskRef: + params: + - name: name + value: deprecated-image-check + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-deprecated-image-check:0.4@sha256:6b1b325de0af29b6e9a0696f4d2b669a1e6a046941726cc97c5e42785aad870c + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + - name: clair-scan + params: + - name: image-digest + value: $(tasks.build-container.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-container.results.IMAGE_URL) + runAfter: + - build-container + taskRef: + params: + - name: name + value: clair-scan + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-clair-scan:0.1@sha256:a6107f78e5fa9e087992f11d788701e4241d9875b153def796fb3bf257c3b7fd + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + - name: sast-snyk-check + runAfter: + - clone-repository + taskRef: + params: + - name: name + value: sast-snyk-check + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-sast-snyk-check:0.1@sha256:b3d2d07394ff983d5f2578c294cd8c4e9428fecc801495feeb929d932c10f740 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + workspaces: + - name: workspace + workspace: workspace + - name: clamav-scan + params: + - name: image-digest + value: $(tasks.build-container.results.IMAGE_DIGEST) + - name: image-url + value: $(tasks.build-container.results.IMAGE_URL) + runAfter: + - build-container + taskRef: + params: + - name: name + value: clamav-scan + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-clamav-scan:0.1@sha256:6ba32717bd837ca0d5714b518cc4530e1f1d5bef137df54c02b0c2151b9d217e + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + - name: sbom-json-check + params: + - name: IMAGE_URL + value: $(tasks.build-container.results.IMAGE_URL) + - name: IMAGE_DIGEST + value: $(tasks.build-container.results.IMAGE_DIGEST) + runAfter: + - build-container + taskRef: + params: + - name: name + value: sbom-json-check + - name: bundle + value: quay.io/redhat-appstudio-tekton-catalog/task-sbom-json-check:0.1@sha256:dbd467a0507cff1981d3c98f683339feaab1b387c5b5fbf1ff957e9be2e27027 + - name: kind + value: task + resolver: bundles + when: + - input: $(params.skip-checks) + operator: in + values: + - "false" + workspaces: + - name: workspace + - name: git-auth + optional: true + taskRunTemplate: {} + workspaces: + - name: workspace + volumeClaimTemplate: + metadata: + creationTimestamp: null + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + status: {} + - name: git-auth + secret: + secretName: '{{ git_auth_secret }}' +status: {} diff --git a/deploy/openshift/template.yaml b/deploy/openshift/template.yaml new file mode 100644 index 0000000..0a6f33b --- /dev/null +++ b/deploy/openshift/template.yaml @@ -0,0 +1,104 @@ +kind: Template +apiVersion: template.openshift.io/v1 +metadata: + name: onguard +labels: + template: onguard +objects: + - kind: ServiceAccount + apiVersion: v1 + metadata: + name: '${SERVICE_ACCOUNT_NAME}' + - apiVersion: batch/v1 + kind: CronJob + metadata: + name: '${APP_NAME}' + spec: + concurrencyPolicy: Forbid + schedule: "${CRONTAB_EXPRESSION}" + jobTemplate: + spec: + template: + spec: + restartPolicy: Never + serviceAccountName: '${SERVICE_ACCOUNT_NAME}' + containers: + - name: '${APP_NAME}' + image: '${IMAGE}:${IMAGE_TAG}' + imagePullPolicy: IfNotPresent + command: + - ./application + - gcs + env: + - name: DB_REDIS_HOST + valueFrom: + secretKeyRef: + name: '${ELASTICACHE_SECRET}' + key: db.host + - name: DB_REDIS_PORT + valueFrom: + secretKeyRef: + name: '${ELASTICACHE_SECRET}' + key: db.port + resources: + limits: + cpu: ${CPU_LIMIT} + memory: ${MEMORY_LIMIT} + requests: + cpu: ${CPU_REQUEST} + memory: ${MEMORY_REQUEST} +parameters: + - name: APP_NAME + displayName: Application name + description: Application name + value: onguard + required: true + - name: IMAGE + displayName: Container image name + description: Container image name + value: quay.io/ecosystem-appeng/osv-ingester + required: true + - name: IMAGE_TAG + displayName: Container image tag + description: Container image tag + value: latest + required: true + - name: SERVICE_ACCOUNT_NAME + displayName: ServiceAccount name + description: The name of the ServiceAccount to use to run this pod. + value: onguard-sa + - name: ELASTICACHE_SECRET + displayName: Elasticache Secret + description: Name of the secret containing the Elasticache settings + value: onguard-elasticache + required: true + - name: SERVICE_NAME + displayName: Service name + description: Service name + value: onguard + required: true + - name: CRONTAB_EXPRESSION + displayName: Crontab expression + description: Crontab expression + value: "30 00,12 * * *" + required: true + - name: CPU_REQUEST + description: The minimum amount of CPU required by a container + displayName: Memory Limit + required: true + value: 5m + - name: CPU_LIMIT + description: The maximum amount of CPU the container can use. + displayName: Memory Limit + required: true + value: 10m + - name: MEMORY_REQUEST + description: The minimum amount of memory required by a container + displayName: Memory Limit + required: true + value: 10Mi + - name: MEMORY_LIMIT + description: The maximum amount of memory the container can use. + displayName: Memory Limit + required: true + value: 100Mi diff --git a/deploy/osv-ingester.yaml b/deploy/osv-ingester.yaml new file mode 100644 index 0000000..dbf3571 --- /dev/null +++ b/deploy/osv-ingester.yaml @@ -0,0 +1,37 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: osv-ingester +spec: + concurrencyPolicy: Forbid + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + restartPolicy: Never + containers: + - name: osv-ingester + image: quay.io/ruben/osv-ingester:latest + imagePullPolicy: IfNotPresent + command: + - ./application + - gcs + env: + - name: DB_REDIS_HOST + valueFrom: + secretKeyRef: + name: exhort-onguard-secret + key: db.host + - name: DB_REDIS_PORT + valueFrom: + secretKeyRef: + name: exhort-onguard-secret + key: db.port + resources: + limits: + cpu: 10m + memory: 100Mi + requests: + cpu: 1m + memory: 50Mi \ No newline at end of file diff --git a/deploy/redis.yaml b/deploy/redis.yaml new file mode 100644 index 0000000..75e5486 --- /dev/null +++ b/deploy/redis.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: docker.io/redis/redis-stack:latest + imagePullPolicy: IfNotPresent + ports: + - name: redis + containerPort: 6379 + protocol: TCP + - name: http + containerPort: 8001 + protocol: TCP + volumeMounts: + - name: logs + mountPath: /redisinsight/logs + - name: data + mountPath: /data + volumes: + - name: logs + emptyDir: {} + - name: data + persistentVolumeClaim: + claimName: redis-data +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + labels: + app: redis +spec: + ports: + - name: redis + port: 6379 + protocol: TCP + targetPort: 6379 + - name: http + port: 8001 + protocol: TCP + targetPort: 8001 + selector: + app: redis +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-data +spec: + storageClassName: gp2 + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi \ No newline at end of file diff --git a/devfile.yaml b/devfile.yaml new file mode 100644 index 0000000..75040c0 --- /dev/null +++ b/devfile.yaml @@ -0,0 +1,32 @@ +schemaVersion: 2.2.0 +metadata: + name: osv-ingester + version: 1.0.0 + provider: Red Hat + supportUrl: https://github.com/RHEcosystemAppEng/osv-ingester/issues + website: https://github.com/RHEcosystemAppEng/osv-ingester + displayName: OSV Ingester + description: OSV Ingester Service that populates the databes data from OSV + tags: + - Exhort + - RHTPA + - Java + - Quarkus + - OSV + projectType: Quarkus + language: Java +parent: + id: java-quarkus + registryUrl: 'https://registry.devfile.io' +components: + - name: image-build + image: + imageName: osv-ingester:latest + dockerfile: + uri: src/main/docker/Dockerfile.multi-stage + buildContext: . + rootRequired: false +commands: + - id: build-image + apply: + component: image-build diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..8d937f4 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c4586b5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c946d65 --- /dev/null +++ b/pom.xml @@ -0,0 +1,413 @@ + + + 4.0.0 + com.redhat.ecosystemappeng.exhort + osv-ingester + 1.0.0-SNAPSHOT + OSV Ingester + Red Hat Trusted Profile Analyser :: Exhort :: OSV Ingester + + + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + true + + 3.2.0 + 3.11.0 + 3.5.0 + 3.1.1 + 3.3.0 + 3.1.0 + 3.0.1 + 1.6.2 + 3.1.2 + 4.1 + 1.9.0 + + + 3.9.1 + 3.4.2 + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + GitHub Issues + https://github.com/RHEcosystemAppEng/osv-ingester/issues + + + + https://github.com/RHEcosystemAppEng/osv-ingester + scm:git:git@github.com:RHEcosystemAppEng/osv-ingester.git + scm:git:git@github.com:RHEcosystemAppEng/osv-ingester.git + + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + ${quarkus.platform.group-id} + quarkus-google-cloud-services-bom + ${quarkus.platform.version} + pom + import + + + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-smallrye-health + + + io.quarkus + quarkus-redis-client + + + io.quarkus + quarkus-redis-cache + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-smallrye-fault-tolerance + + + io.quarkus + quarkus-scheduler + + + io.quarkiverse.googlecloudservices + quarkus-google-cloud-storage + + + io.quarkus + quarkus-elytron-security-properties-file + + + io.quarkus + quarkus-picocli + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-junit5-mockito + test + + + io.rest-assured + rest-assured + test + + + org.wiremock + wiremock-standalone + ${wiremock.version} + test + + + + + + + + maven-clean-plugin + ${maven-clean-plugin.version} + + + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + maven-dependency-plugin + ${maven-dependency-plugin.version} + + + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + + com.mycila + license-maven-plugin + ${license-maven-plugin.version} + + + Red Hat, Inc. and/or its affiliates + + + + + **/module-info.java + **/package-info.java + **/module-info.test + src/test/resources/** + src/main/resources/** + src/it/**/test/resources/** + src/it/**/main/resources/** + + + src/main/** + src/test/** + src/it/**/src/main/** + src/it/**/src/test/** + + + + + + + false + + + + net.revelc.code + impsort-maven-plugin + ${impsort-maven-plugin.version} + + java.,javax.,org.,com. + java,* + + + + sort-imports + + check + + + + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + maven-release-plugin + ${maven-release-plugin.version} + + -Pprepare-deployment,sign-deployment + v@{project.version} + build(release): + + + + maven-deploy-plugin + ${maven-deploy-plugin.version} + + true + + + + maven-enforcer-plugin + + + + enforce + + + + + + org.codehaus.mojo + extra-enforcer-rules + ${extra-enforcer-rules.version} + + + + + + + [11,) + + + [3.8,) + + + + compile + provided + + + + + + + + com.mycila + license-maven-plugin + + + + check + + + + + + net.revelc.code + impsort-maven-plugin + + + + + + native + + + native + + + + false + native + + + + prepare-deployment + + + + maven-source-plugin + + + + jar-no-fork + + + + + + + + + sign-deployment + + + + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + \ No newline at end of file diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..896adf1 --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,95 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/osv-ingester-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/osv-ingester-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/osv-ingester-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.redhat.io/ubi8/openjdk-17:1.16 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS="-Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + diff --git a/src/main/docker/Dockerfile.multi-stage b/src/main/docker/Dockerfile.multi-stage new file mode 100644 index 0000000..94b1ba2 --- /dev/null +++ b/src/main/docker/Dockerfile.multi-stage @@ -0,0 +1,35 @@ +## Stage 1 : build with maven builder image with native capabilities +FROM registry.redhat.io/quarkus/mandrel-23-rhel8:23.0 AS build + +COPY --chown=quarkus:quarkus mvnw /code/mvnw +COPY --chown=quarkus:quarkus .mvn /code/.mvn +COPY --chown=quarkus:quarkus pom.xml /code/ + +USER quarkus +WORKDIR /code +RUN ./mvnw -B org.apache.maven.plugins:maven-dependency-plugin:3.6.1:go-offline +COPY --chown=quarkus:quarkus src /code/src +RUN ./mvnw package -B -Pnative -DskipTests=true + +## Stage 2 : create the docker final image +FROM registry.redhat.io/ubi9/ubi-minimal:9.4 + +LABEL description="Red Hat Trusted Profile Analyzer - OSV Ingester Service" +LABEL io.k8s.description="Red Hat Trusted Profile Analyzer - OSV Ingester Service" +LABEL io.k8s.display-name="RHTPA OSV Ingester service" +LABEL io.openshift.tags="rhtpa exhort osv-ingester" +LABEL summary="The RHTPA OSV Ingester Service ingests the OSV public source into the ONGuard Database" + +WORKDIR /work/ +COPY --from=build /code/target/*-runner /work/application + +# set up permissions for user `1001` +RUN chmod 775 /work /work/application \ + && chown -R 1001 /work \ + && chmod -R "g+rwX" /work \ + && chown -R 1001:root /work + +EXPOSE 8080 +USER 1001 + +CMD ["./application"] diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..3be8c71 --- /dev/null +++ b/src/main/docker/Dockerfile.native @@ -0,0 +1,27 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./mvnw package -Pnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/osv-ingester . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/osv-ingester +# +### +FROM registry.redhat.io/ubi9/ubi-minimal:9.3 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +CMD ["./application"] diff --git a/src/main/docker/Dockerfile.native-micro b/src/main/docker/Dockerfile.native-micro new file mode 100644 index 0000000..38bb7d0 --- /dev/null +++ b/src/main/docker/Dockerfile.native-micro @@ -0,0 +1,30 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# It uses a micro base image, tuned for Quarkus native executables. +# It reduces the size of the resulting container image. +# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. +# +# Before building the container image run: +# +# ./mvnw package -Pnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/osv-ingester . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/osv-ingester gcs --ecosystem Maven +# +### +FROM quay.io/quarkus/quarkus-micro-image:2.0 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +CMD ["./application"] diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/GCSIngesterCommand.java b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/GCSIngesterCommand.java new file mode 100644 index 0000000..96c93e8 --- /dev/null +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/GCSIngesterCommand.java @@ -0,0 +1,43 @@ +/* + * Copyright ${year} Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.redhat.ecosystemappeng.onguard.osvingester.cli; + +import com.redhat.ecosystemappeng.onguard.osvingester.service.EcosystemLoader; + +import picocli.CommandLine; + +@CommandLine.Command(name = "gcs", description = "Loads Vulnerability data from OSV Google Cloud Storage Bucket") +public class GCSIngesterCommand implements Runnable { + @CommandLine.Option(names = { "--ecosystem" }, description = "Ecosystem to load", defaultValue = "all") + String ecosystem; + + private final EcosystemLoader loader; + + public GCSIngesterCommand(EcosystemLoader loader) { + this.loader = loader; + } + + @Override + public void run() { + if ("all".equalsIgnoreCase(ecosystem)) { + loader.loadAll().collect().asList().await().indefinitely(); + } else { + loader.load(ecosystem).await().indefinitely(); + } + } +} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/OsvIngesterCommand.java b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/OsvIngesterCommand.java new file mode 100644 index 0000000..a718055 --- /dev/null +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/cli/OsvIngesterCommand.java @@ -0,0 +1,27 @@ +/* + * Copyright ${year} Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.redhat.ecosystemappeng.onguard.osvingester.cli; + +import io.quarkus.picocli.runtime.annotations.TopCommand; +import picocli.CommandLine; + +@TopCommand +@CommandLine.Command(name = "osv-ingester", mixinStandardHelpOptions = true, subcommands = {GCSIngesterCommand.class}) +public class OsvIngesterCommand { + +} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/model/IngestionReport.java b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/model/IngestionReport.java new file mode 100644 index 0000000..8a80518 --- /dev/null +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/model/IngestionReport.java @@ -0,0 +1,24 @@ +/* + * Copyright ${year} Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.redhat.ecosystemappeng.onguard.osvingester.model; + +import java.util.List; + +public record IngestionReport(String name, int total, List withoutSeverity) { + +} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/EcosystemLoader.java b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/EcosystemLoader.java new file mode 100644 index 0000000..51234f2 --- /dev/null +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/EcosystemLoader.java @@ -0,0 +1,31 @@ +/* + * Copyright ${year} Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.redhat.ecosystemappeng.onguard.osvingester.service; + +import com.redhat.ecosystemappeng.onguard.osvingester.model.IngestionReport; + +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +public interface EcosystemLoader { + + Multi loadAll(); + + Uni load(String ecosystem); + +} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/VulnerabilityIngester.java b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/VulnerabilityIngester.java new file mode 100644 index 0000000..f11e013 --- /dev/null +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/VulnerabilityIngester.java @@ -0,0 +1,116 @@ +/* + * Copyright ${year} Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.redhat.ecosystemappeng.onguard.osvingester.service; + +import java.io.IOException; + +import org.jboss.logging.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +import io.quarkus.redis.datasource.RedisDataSource; +import io.quarkus.redis.datasource.value.ValueCommands; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class VulnerabilityIngester { + + final private ValueCommands vulnCommands; + final private ObjectMapper objectMapper; + + private final static Logger LOGGER = Logger.getLogger(VulnerabilityIngester.class); + + VulnerabilityIngester(RedisDataSource ds, ObjectMapper objectMapper) { + this.vulnCommands = ds.value(String.class); + this.objectMapper = objectMapper; + } + + public String save(String source, byte[] content) { + JsonNode obj; + try { + obj = objectMapper.readTree(content); + var id = obj.get("id").asText(); + var hasSeverity = obj.get("severity") != null; + if (hasSeverity) { + vulnCommands.set(vulnKey(id), obj.toString()); + } else { + var existingRaw = vulnCommands.get(vulnKey(id)); + if (existingRaw == null) { + vulnCommands.set(vulnKey(id), obj.toString()); + } else { + var existingHasSeverity = objectMapper.readTree(existingRaw).has("severity"); + if (!existingHasSeverity) { + vulnCommands.set(vulnKey(id), obj.toString()); + } + } + } + if (!hasSeverity) { + return id; + } + return null; + } catch (IOException e) { + LOGGER.errorf("Unable to parse Json vulnerability: %s", source, e); + return null; + } + } + + public String get(String vulnId) { + return vulnCommands.get(vulnKey(vulnId)); + } + + public boolean reconcile(String vulnId) { + var vuln = get(vulnId); + try { + var obj = objectMapper.readTree(vuln); + var aliases = obj.withArray("aliases"); + for (var aliasId : aliases) { + var alias = get(aliasId.asText()); + if (alias != null) { + try { + var aliasObj = objectMapper.readTree(alias); + if (aliasObj.has("severity")) { + LOGGER.debugf("Must reconcile %s and %s", vulnId, aliasId); + var merged = mergeSeverity(obj, aliasObj); + vulnCommands.set(vulnKey(vulnId), merged.toPrettyString()); + return true; + } + } catch (JsonProcessingException e) { + LOGGER.error("Unable to parse Json vulnerability: %s", aliasId, e); + } + } + } + } catch (JsonProcessingException e) { + LOGGER.error("Unable to parse Json vulnerability: %s", vulnId, e); + } + return false; + } + + private String vulnKey(String id) { + return "vuln:" + id; + } + + private JsonNode mergeSeverity(JsonNode base, JsonNode source) { + var merged = JsonNodeFactory.instance.objectNode(); + base.fields().forEachRemaining(entry -> merged.set(entry.getKey(), entry.getValue())); + merged.set("severity", source.get("severity")); + return merged; + } +} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/googlecloudstorage/GCSLoader.java b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/googlecloudstorage/GCSLoader.java new file mode 100644 index 0000000..6b8730c --- /dev/null +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/osvingester/service/googlecloudstorage/GCSLoader.java @@ -0,0 +1,143 @@ +/* + * Copyright ${year} Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.redhat.ecosystemappeng.onguard.osvingester.service.googlecloudstorage; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.logging.Logger; + +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.Storage; +import com.redhat.ecosystemappeng.onguard.osvingester.model.IngestionReport; +import com.redhat.ecosystemappeng.onguard.osvingester.service.EcosystemLoader; +import com.redhat.ecosystemappeng.onguard.osvingester.service.VulnerabilityIngester; + +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import io.smallrye.mutiny.infrastructure.Infrastructure; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GCSLoader implements EcosystemLoader { + + private static final Logger LOGGER = Logger.getLogger(GCSLoader.class); + + private final VulnerabilityIngester vulnerabilityIngester; + private final Bucket bucket; + private final ExecutorService executor = Executors.newFixedThreadPool(20); + + GCSLoader(Storage storage, + @ConfigProperty(name = "import.osv-bucket", defaultValue = "osv-vulnerabilities") String osvBucketName, + VulnerabilityIngester vulnerabilityIngester) { + this.bucket = storage.get(osvBucketName); + this.vulnerabilityIngester = vulnerabilityIngester; + } + + private Stream listEcosystems() { + var ecosystems = bucket.get("ecosystems.txt"); + var content = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(ecosystems.getContent()))); + + return content.lines(); + } + + public Multi loadAll() { + return Multi.createFrom() + .items(listEcosystems()) + .onItem() + .transformToUniAndMerge(this::load) + .runSubscriptionOn(executor) + .onCompletion() + .invoke(() -> LOGGER.info("Completed load of all ecosystems")); + } + + public Uni load(String ecosystem) { + return Uni.createFrom().item(bucket.get(ecosystem + "/all.zip")) + .emitOn(Infrastructure.getDefaultWorkerPool()) + .onItem() + .transform(all -> { + LOGGER.infof("Loading [%s]", ecosystem); + return load(ecosystem, new ByteArrayInputStream(all.getContent())); + }) + .onFailure().retry().withBackOff(Duration.ofSeconds(2)).atMost(5) + .onFailure().recoverWithItem(e -> { + LOGGER.error("Failed to load vulnerabilities for: " + ecosystem, e); + return new IngestionReport(ecosystem, 0, Collections.emptyList()); + }) + .onItem() + .transform(report -> { + List missing = new ArrayList<>(); + for (var item : report.withoutSeverity()) { + if (!vulnerabilityIngester.reconcile(item)) { + missing.add(item); + } + } + return new IngestionReport(report.name(), report.total(), missing); + }).onItem() + .invoke(report -> LOGGER.infof("Completed [%s] / Total vulnerabilities [%s] / Without severity [%s]", + report.name(), report.total(), report.withoutSeverity().size())); + } + + private IngestionReport load(String ecosystem, ByteArrayInputStream allFile) { + var count = 0; + List incomplete = new ArrayList<>(); + try (var zis = new ZipInputStream(allFile)) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (!entry.isDirectory() && entry.getName().endsWith(".json")) { + var content = readZipEntryContent(zis); + count++; + var incompleteVuln = vulnerabilityIngester.save(entry.getName(), content); + if (incompleteVuln != null) { + incomplete.add(incompleteVuln); + } + } + } + zis.closeEntry(); + } catch (IOException e) { + LOGGER.error("Unable to process Zip file: " + ecosystem, e); + } + return new IngestionReport(ecosystem, count, incomplete); + } + + // Utility method to read the content of a ZipEntry + private byte[] readZipEntryContent(InputStream zipInputStream) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = zipInputStream.read(buffer)) > 0) { + byteArrayOutputStream.write(buffer, 0, length); + } + return byteArrayOutputStream.toByteArray(); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..8eb4c6a --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,12 @@ +quarkus.banner.enabled=false +quarkus.http.host-enabled=false + +quarkus.redis.max-pool-waiting=100 + +%prod.quarkus.redis.hosts=redis://${db.redis.host:localhost}:${db.redis.port:6379}/ + +%prod.quarkus.log.level=ERROR +%prod.quarkus.log.category."com.redhat.ecosystemappeng.onguard.osvingester".level=INFO +%prod.quarkus.log.category."com.redhat.ecosystemappeng.onguard.osvingester".handlers=cli-handler +%prod.quarkus.log.handler.console.cli-handler.format=%d{yyyy-MM-dd HH:mm:ss} %m%n +%prod.quarkus.log.category."com.redhat.ecosystemappeng.onguard.osvingester".use-parent-handlers=false \ No newline at end of file