diff --git a/.github/workflows/_build+publish-pds-solution.yml b/.github/workflows/_build+publish-pds-solution.yml index 70719bc656..86232d5fc2 100644 --- a/.github/workflows/_build+publish-pds-solution.yml +++ b/.github/workflows/_build+publish-pds-solution.yml @@ -40,7 +40,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Docker login to ghcr.io - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -68,6 +68,7 @@ jobs: export OWASPZAP_VERSION export OWASPZAP_SHA256SUM export OWASPZAP_WRAPPER_VERSION + export PREPARE_WRAPPER_VERSION export PMD_VERSION export SCANCODE_VERSION export SPDX_TOOL_VERSION diff --git a/.github/workflows/build+publish-all-pds-solutions.yml b/.github/workflows/build+publish-all-pds-solutions.yml index 4728f1116b..0396b48193 100644 --- a/.github/workflows/build+publish-all-pds-solutions.yml +++ b/.github/workflows/build+publish-all-pds-solutions.yml @@ -62,6 +62,12 @@ jobs: pds-solution: owaspzap pds-version: ${{ inputs.pds-version }} + call_build_pds-prepare: + uses: mercedes-benz/sechub/.github/workflows/_build+publish-pds-solution.yml@develop + with: + pds-solution: prepare + pds-version: ${{ inputs.pds-version }} + call_build-and-publish-pmd: uses: mercedes-benz/sechub/.github/workflows/_build+publish-pds-solution.yml@develop with: diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index e14f1c0a97..739927575b 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -37,18 +37,18 @@ jobs: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false - name: Set up Go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 with: go-version: 1.21.6 @@ -105,7 +105,7 @@ jobs: - name: Update documentation - Create pull request if: (inputs.publish-documentation != '') && (github.ref_name == env.ACTIONS_SECHUB_DOC_RELEASE_BRANCH) id: pr_release_documentation - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-documentation branch-suffix: short-commit-hash diff --git a/.github/workflows/github-action-scan.yml b/.github/workflows/github-action-scan.yml index 021fd7142b..e504b75505 100644 --- a/.github/workflows/github-action-scan.yml +++ b/.github/workflows/github-action-scan.yml @@ -23,7 +23,7 @@ jobs: - name: Use Node.js # We do not define a dedicated node version here, we just use the default environment # which should be the default environment for the github actions runtime as well - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b - name: Clean install run: npm ci @@ -64,7 +64,7 @@ jobs: key: ${{ runner.os }}-pds-${{ env.pds_version }} - name: Set up JDK 17 (to run servers) - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 4116cdf836..7c0312b196 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -19,18 +19,18 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false - name: Set up Go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 with: go-version: 1.21.6 diff --git a/.github/workflows/publish-libraries.yml b/.github/workflows/publish-libraries.yml index a29ecf7aed..d27a04e157 100644 --- a/.github/workflows/publish-libraries.yml +++ b/.github/workflows/publish-libraries.yml @@ -28,13 +28,13 @@ jobs: # Build - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false diff --git a/.github/workflows/release-client-server-pds.yml b/.github/workflows/release-client-server-pds.yml index 7ac2eff00d..5683de4363 100644 --- a/.github/workflows/release-client-server-pds.yml +++ b/.github/workflows/release-client-server-pds.yml @@ -91,18 +91,18 @@ jobs: # Setup + Caching # ---------------------- - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false - name: Set up Go - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 with: go-version: 1.21.6 @@ -118,7 +118,7 @@ jobs: ${{ runner.os }}-go- - name: Docker login to ghcr.io - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -143,7 +143,7 @@ jobs: - name: Create pull request for SPDX license headers id: pr_spdx_headers if: steps.apply-headers.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-spdx-headers branch-suffix: short-commit-hash @@ -295,7 +295,7 @@ jobs: # ----------------------------------------- - name: Create pull request for release documentation id: pr_release_documentation - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-documentation branch-suffix: short-commit-hash diff --git a/.github/workflows/release-github-action.yml b/.github/workflows/release-github-action.yml index 43409580a3..284f1a3d18 100644 --- a/.github/workflows/release-github-action.yml +++ b/.github/workflows/release-github-action.yml @@ -48,7 +48,7 @@ jobs: - name: Use Node.js # We do not define a dedicated node version here, we just use the default environment # which should be the default environment for the github actions runtime as well - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b - name: Git setup run: | @@ -69,7 +69,7 @@ jobs: - name: Create pull request for SPDX license headers id: pr_spdx_headers if: steps.apply-headers.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-spdx-headers branch-suffix: short-commit-hash @@ -130,7 +130,7 @@ jobs: - name: Create pull request for SecHub Github Action release id: pr_gha-release if: steps.github-actions_commit.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-github-action branch-suffix: short-commit-hash diff --git a/.github/workflows/release-pds-tools.yml b/.github/workflows/release-pds-tools.yml index fb6a2c4c96..351c1ab1db 100644 --- a/.github/workflows/release-pds-tools.yml +++ b/.github/workflows/release-pds-tools.yml @@ -47,13 +47,13 @@ jobs: # Setup + Caching # ---------------------- - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false @@ -73,7 +73,7 @@ jobs: - name: Create pull request for SPDX license headers id: pr_spdx_headers if: steps.apply-headers.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-spdx-headers branch-suffix: short-commit-hash diff --git a/.github/workflows/release-webui.yml b/.github/workflows/release-webui.yml index e62b56878c..80bb91ee60 100644 --- a/.github/workflows/release-webui.yml +++ b/.github/workflows/release-webui.yml @@ -58,18 +58,18 @@ jobs: # Setup + Caching # ---------------------- - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false - name: Docker login to ghcr.io - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -91,7 +91,7 @@ jobs: - name: Create pull request for SPDX license headers id: pr_spdx_headers if: steps.apply-headers.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-spdx-headers branch-suffix: short-commit-hash diff --git a/.github/workflows/release-wrapper-checkmarx.yml b/.github/workflows/release-wrapper-checkmarx.yml index ec9c88019c..3810c9cb27 100644 --- a/.github/workflows/release-wrapper-checkmarx.yml +++ b/.github/workflows/release-wrapper-checkmarx.yml @@ -37,13 +37,13 @@ jobs: # Setup + Caching # ---------------------- - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false @@ -63,7 +63,7 @@ jobs: - name: Create a pull request for SPDX license headers id: pr_spdx_headers if: steps.apply-headers.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-spdx-headers branch-suffix: short-commit-hash diff --git a/.github/workflows/release-wrapper-owaspzap.yml b/.github/workflows/release-wrapper-owaspzap.yml index 331c7cdb6b..bbc5459ba8 100644 --- a/.github/workflows/release-wrapper-owaspzap.yml +++ b/.github/workflows/release-wrapper-owaspzap.yml @@ -38,13 +38,13 @@ jobs: # Setup + Caching # ---------------------- - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false @@ -64,7 +64,7 @@ jobs: - name: Create a pull request for SPDX license headers id: pr_spdx_headers if: steps.apply-headers.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-spdx-headers branch-suffix: short-commit-hash diff --git a/.github/workflows/release-wrapper-prepare.yml b/.github/workflows/release-wrapper-prepare.yml new file mode 100644 index 0000000000..baa49de1cb --- /dev/null +++ b/.github/workflows/release-wrapper-prepare.yml @@ -0,0 +1,213 @@ +# SPDX-License-Identifier: MIT +name: Release wrapper for Prepare + +on: + workflow_dispatch: + inputs: + actor-email: + description: Insert your email address here. It will be used in the generated pull requests + required: true + prepare-wrapper-version: + description: Prepare-wrapper Version (e.g. 1.0.0) + required: true + prepare-wrapper-milestone-number: + description: Prepare-wrapper Milestone number (e.g. 91) + required: true +jobs: + release-version: + name: Create Prepare-wrapper release + runs-on: ubuntu-latest + steps: + - name: "Show Inputs" + run: | + echo "actor-email: '${{ inputs.actor-email }}'" + echo "Prepare-wrapper '${{ inputs.prepare-wrapper-version }}' - Milestone '${{ inputs.prepare-wrapper-milestone-number }}'" + + - name: Checkout branch master + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + with: + ref: master + + # Create temporary local tags, so we build documentation for this tag... + # The final tag on git server side will be done automatically by the release when the draft is saved as "real" release + - name: Tag Prepare Wrapper version v${{ inputs.prepare-wrapper-version }}-prepare-wrapper (temporarily) + run: git tag v${{ inputs.prepare-wrapper-version }}-prepare-wrapper + + # ---------------------- + # Setup + Caching + # ---------------------- + - name: Set up JDK 17 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 + with: + java-version: 17 + distribution: temurin + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + with: + cache-read-only: false + + # ---------------------- + # Create a pull request if license headers are missing + # ---------------------- + - name: run apply-headers.sh + id: apply-headers + run: | + git config user.name "$GITHUB_TRIGGERING_ACTOR (via github-actions)" + git config user.email "${{ inputs.actor-email }}" + ./apply-headers.sh + git commit -am "SPDX headers added by SecHub release job @github-actions" || true + COMMITS=`git log --oneline --branches --not --remotes` + echo "commits=$COMMITS" >> $GITHUB_OUTPUT + + - name: Create a pull request for SPDX license headers + id: pr_spdx_headers + if: steps.apply-headers.outputs.commits != '' + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c + with: + branch: release-spdx-headers + branch-suffix: short-commit-hash + delete-branch: true + title: '0 - Before prepare-wrapper release: Add missing SPDX license headers [auto-generated]' + body: | + Auto-generated by Github Actions prepare-wrapper release job. + + -> Please review and merge **before** publishing the prepare-wrapper release. + + - name: Print PR infos + if: steps.apply-headers.outputs.commits != '' + run: | + echo "Pull Request Number - ${{ steps.pr_spdx_headers.outputs.pull-request-number }}" + echo "Pull Request URL - ${{ steps.pr_spdx_headers.outputs.pull-request-url }}" + + - name: Switch back to master branch + run: git checkout master + + # ----------------------------------------- + # Build SecHub Prepare Wrapper + # ----------------------------------------- + - name: Build Prepare Wrapper + run: ./gradlew :sechub-wrapper-prepare:bootjar + + # ----------------------------------------- + # Upload build artifacts + # ----------------------------------------- + - name: Inspect GIT status + if: always() + run: | + mkdir build/reports -p + git status > build/reports/git-status.txt + + - name: Archive GIT status + if: always() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: git-status.txt + path: build/reports/git-status.txt + retention-days: 14 + + - name: Archive Prepare Wrapper libs directory + if: always() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: sechub-wrapper-prepare + path: sechub-wrapper-prepare/build/libs + retention-days: 14 + + - name: Switch back to master branch + run: git checkout master + + # ----------------------------------------- + # Assert releaseable, so no dirty flags on releases + # even when all artifact creation parts are done! + # ----------------------------------------- + - name: Assert releasable + run: ./gradlew assertReleaseable + + - name: Create Prepare Wrapper release + id: create_prepare-wrapper_release + uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: v${{ inputs.prepare-wrapper-version }}-prepare-wrapper + commitish: master + release_name: Prepare Wrapper Version ${{ inputs.prepare-wrapper-version }} + body: | + Changes in this Release + - Some minor changes on Prepare Wrapper implementation + + For more details please look at [Milestone ${{inputs.prepare-wrapper-milestone-number}}]( https://github.com/mercedes-benz/sechub/milestone/${{inputs.prepare-wrapper-milestone-number}}?closed=1) + draft: true + prerelease: false + + # ----------------------------------------- + # Upload release artifacts + # ----------------------------------------- + - name: Create files and sha256 checksum for Prepare Wrapper jar + run: | + cd sechub-wrapper-prepare/build/libs/ + sha256sum sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar > sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar.sha256sum + + - name: Upload asset sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_prepare-wrapper_release.outputs.upload_url }} + asset_path: sechub-wrapper-prepare/build/libs/sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar + asset_name: sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar + asset_content_type: application/zip + + - name: Upload asset sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar.sha256sum + uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_prepare-wrapper_release.outputs.upload_url }} + asset_path: sechub-wrapper-prepare/build/libs/sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar.sha256sum + asset_name: sechub-wrapper-prepare-${{ inputs.prepare-wrapper-version }}.jar.sha256sum + asset_content_type: text/plain + + # ----------------------------------------- + # Create release issue + # ----------------------------------------- + - name: Create Prepare Wrapper ${{ inputs.prepare-wrapper-version }} release issue + uses: dacbd/create-issue-action@main + with: + token: ${{ github.token }} + title: Release Prepare Wrapper ${{ inputs.prepare-wrapper-version }} + body: | + See [Milestone ${{inputs.prepare-wrapper-milestone-number}}]( https://github.com/mercedes-benz/sechub/milestone/${{inputs.prepare-wrapper-milestone-number}}?closed=1) for details. + + Please close this issue after the release. + milestone: ${{ inputs.prepare-wrapper-milestone-number }} + + # ----------------------------------------- + # Create a pull request for merging back `master` into `develop` + # ----------------------------------------- + - name: pull-request master to develop + id: pr_master_to_develop + continue-on-error: true + uses: repo-sync/pull-request@7e79a9f5dc3ad0ce53138f01df2fad14a04831c5 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + source_branch: "master" + destination_branch: "develop" + pr_allow_empty: true # should allow an empty PR, but seems not to work + pr_title: '2 - After Prepare Wrapper release: Merge master back into develop [auto-generated]' + pr_body: | + Merge master branch back into develop + + -> Please merge **after** the release has been published. + + - name: Print PR infos if PR was created + if: steps.pr_master_to_develop.outcome == 'success' + run: | + echo "Pull Request Number - ${{ steps.pr_master_to_develop.outputs.pr_number }}" + echo "Pull Request URL - ${{ steps.pr_master_to_develop.outputs.pr_url }}" + + - name: Print info if no PR was created + if: steps.pr_master_to_develop.outcome != 'success' + run: | + echo "Nothing to merge - no pull request necessary." diff --git a/.github/workflows/release-wrapper-xray.yml b/.github/workflows/release-wrapper-xray.yml index d73fab1fb4..8f50bd05c9 100644 --- a/.github/workflows/release-wrapper-xray.yml +++ b/.github/workflows/release-wrapper-xray.yml @@ -37,13 +37,13 @@ jobs: # Setup + Caching # ---------------------- - name: Set up JDK 17 - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 + uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 with: java-version: 17 distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 with: cache-read-only: false @@ -63,7 +63,7 @@ jobs: - name: Create a pull request for SPDX license headers id: pr_spdx_headers if: steps.apply-headers.outputs.commits != '' - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: branch: release-spdx-headers branch-suffix: short-commit-hash diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd8d8f3c04..8f9b0aa5f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,8 +95,6 @@ This depends on you which kind of IDE you use for development. But we have some Please check our [development-setup documentation](https://mercedes-benz.github.io/sechub/latest/sechub-techdoc.html#development-setup) for details. -Please look into https://github.com/mercedes-benz/sechub/issues/168 for source formatter config files and description for usage. - ### Coding conventions Please look at our [coding-conventions documentation](https://mercedes-benz.github.io/sechub/latest/sechub-techdoc.html#section-coding-conventions) for details. diff --git a/build.gradle b/build.gradle index 5cadc78fee..e346a2e368 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ plugins { id 'org.asciidoctor.jvm.pdf' version '3.3.2' // open api - id 'org.openapi.generator' version '6.5.0' + id 'org.openapi.generator' version '7.7.0' // spring id 'org.springframework.boot' version '3.2.2' apply false @@ -83,7 +83,7 @@ task internalCleanRootBuildFolder(type: Delete){ doFirst { def rootBuildFolder = file("${project.projectDir}/build") if (! rootBuildFolder.exists()){ - rootBuildFolder.mkdirs(); + rootBuildFolder.mkdirs() } delete rootBuildFolder.listFiles() // so we do NOT clear buildSrc/build here! } diff --git a/github-actions/scan/action.yml b/github-actions/scan/action.yml index f0fb606a1b..ecfc17ab49 100644 --- a/github-actions/scan/action.yml +++ b/github-actions/scan/action.yml @@ -52,7 +52,7 @@ inputs: debug: description: 'SecHub debug output on/off' required: false - default: false + default: ${{ runner.debug == 1 }} fail-job-with-findings: description: 'Job will be marked as failed if SecHub finds something' required: false diff --git a/gradle/build-versioning.gradle b/gradle/build-versioning.gradle index 82048a5efc..4aaafad08a 100644 --- a/gradle/build-versioning.gradle +++ b/gradle/build-versioning.gradle @@ -82,9 +82,9 @@ def buildVersionFiles(){ def clientVersionInfo = versionData.defineVersion("Client",buildVersionString(clientVersionCommitTag, hasChanged,buildNumber),latestClientVersion) // write version code for go client - String clientGoVersionTemplate = new File('./sechub-cli/src/mercedes-benz.com/sechub/cli/version.go.template').getText('UTF-8') + String clientGoVersionTemplate = new File(project.rootDir, '/sechub-cli/src/mercedes-benz.com/sechub/cli/version.go.template').getText('UTF-8') String clientGoVersionCode = clientGoVersionTemplate.replaceAll("__version__",clientVersionInfo.getFullVersion()) - def clientVersionFile = new File('./sechub-cli/src/mercedes-benz.com/sechub/cli/version.go') + def clientVersionFile = new File(project.rootDir, './sechub-cli/src/mercedes-benz.com/sechub/cli/version.go') clientVersionFile.write(clientGoVersionCode) // Latest tagged client version as asciidoc file (#2285) @@ -93,7 +93,7 @@ def buildVersionFiles(){ if (latestClientVersion != clientVersionInfo.getShortVersion()) { latestClientVersionText = latestClientVersion + " modified (commit " + currentGitCommit + ")" } - def clientVersionAsciiDocFile = new File('./sechub-doc/src/docs/asciidoc/documents/gen/client-version.adoc') + def clientVersionAsciiDocFile = new File(project.rootDir, './sechub-doc/src/docs/asciidoc/documents/gen/client-version.adoc') clientVersionAsciiDocFile.write("// SPDX-License-Identifier: MIT\n:revnumber: Client "+latestClientVersionText+"\n:longrevnumber: Client "+latestClientVersionText+" - Build date: "+docsTimeStamp+"\n") // ------------------------ @@ -118,7 +118,7 @@ def buildVersionFiles(){ if (latestServerVersion != serverVersionInfo.getShortVersion()) { latestServerVersionText = latestServerVersion + " modified (commit " + currentGitCommit + ")" } - def serverVersionAsciiDocFile = new File('./sechub-doc/src/docs/asciidoc/documents/gen/server-version.adoc') + def serverVersionAsciiDocFile = new File(project.rootDir, './sechub-doc/src/docs/asciidoc/documents/gen/server-version.adoc') serverVersionAsciiDocFile.write("// SPDX-License-Identifier: MIT\n:revnumber: Server "+latestServerVersionText+"\n:longrevnumber: Server "+latestServerVersionText+" - Build date: "+docsTimeStamp+"\n") // ------------------------ @@ -143,7 +143,7 @@ def buildVersionFiles(){ if (latestPDSVersion != pdsVersionInfo.getShortVersion()) { latestPDSVersionText = latestPDSVersion + " modified (commit " + currentGitCommit + ")" } - def pdsVersionAsciiDocFile = new File('./sechub-doc/src/docs/asciidoc/documents/gen/pds-version.adoc') + def pdsVersionAsciiDocFile = new File(project.rootDir, './sechub-doc/src/docs/asciidoc/documents/gen/pds-version.adoc') pdsVersionAsciiDocFile.write("// SPDX-License-Identifier: MIT\n:revnumber: PDS "+latestPDSVersionText+"\n:longrevnumber: PDS "+latestPDSVersionText+" - Build date: "+docsTimeStamp+"\n") // ------------------------ @@ -261,9 +261,9 @@ def buildVersionFiles(){ def stop = new Date() - println(clientVersionInfo.describe()); - println(serverVersionInfo.describe()); - println(webuiVersionInfo.describe()); + println(clientVersionInfo.describe()) + println(serverVersionInfo.describe()) + println(webuiVersionInfo.describe()) println(pdsVersionInfo.describe()) println(pdsToolsVersionInfo.describe()) println(librariesVersionInfo.describe()) @@ -327,9 +327,9 @@ def buildVersionString(commitTag, boolean hasChanged, buildNumber){ calcversion = calcversion - 'v' } if (hasChanged){ - calcversion = "${calcversion}-dirty"; + calcversion = "${calcversion}-dirty" } - calcversion = "${calcversion}-${buildNumber}"; + calcversion = "${calcversion}-${buildNumber}" return calcversion } diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 65357a870e..5d5bfd35b8 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -89,8 +89,15 @@ ext { cyclonedx_gradle_plugin: "1.7.4", /* Prepare wrapper */ - jgit_core: "6.9.0.202403050737-r" - + jgit_core: "6.9.0.202403050737-r", + + /* ArchUnit */ + arch_unit: "1.3.0", + + /* encryption */ + // https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on + bouncy_castle_bcprov_jdk8: "1.78.1" + ] library = [ @@ -187,7 +194,12 @@ ext { /* cycloneDX core for Xray and sechub importer */ cycloneDX_core: "org.cyclonedx:cyclonedx-core-java:${libraryVersion.cycloneDX_core}", - jgit_core: "org.eclipse.jgit:org.eclipse.jgit:${libraryVersion.jgit_core}" + jgit_core: "org.eclipse.jgit:org.eclipse.jgit:${libraryVersion.jgit_core}", + + arch_unit: "com.tngtech.archunit:archunit-junit5:${libraryVersion.arch_unit}", + + bouncy_castle_bcprov_jdk8: "org.bouncycastle:bcprov-jdk18on:${libraryVersion.bouncy_castle_bcprov_jdk8}" + ] diff --git a/gradle/projects.gradle b/gradle/projects.gradle index 8a47d4fdad..1f85b461d3 100644 --- a/gradle/projects.gradle +++ b/gradle/projects.gradle @@ -10,6 +10,8 @@ projectType = [ ], javaProjects: [ project(':sechub-openapi-java'), + project(':sechub-openapi-java-client'), + project(':sechub-api-java'), project(':sechub-testframework'), project(':sechub-analyzer-cli'), project(':sechub-commons-core'), @@ -17,17 +19,21 @@ projectType = [ project(':sechub-commons-model-testframework'), project(':sechub-commons-pds'), project(':sechub-commons-archive'), + project(':sechub-commons-encryption'), project(':sechub-storage-core'), project(':sechub-wrapper-owasp-zap'), project(':sechub-pds-commons-core'), project(':sechub-wrapper-xray'), + project(':sechub-systemtest'), + project(':sechub-pds-tools'), + project(':sechub-examples:example-sechub-api-java'), ], - + publishedLibraries: [ project(':sechub-commons-core'), project(':sechub-commons-model'), ], - + /* any project using spring parts but not already in other spring boot group must be defined here */ springBootProjects: [ @@ -35,20 +41,23 @@ projectType = [ project(':sechub-integrationtest'), project(':sechub-developertools'), project(':sechub-test'), - + project(':sechub-storage-sharedvolume-spring'), - + /* next projects added as spring boot projects only, because otherwise we get the (older) dependencies from aws as compiled projects in IDEs: */ project(':sechub-storage-s3-aws'), project(':sechub-storage-s3-aws-test'), - + /* special wrapper applications which are also spring boot applications to have dependency injection and access to some other spring boot parts */ project(':sechub-wrapper-checkmarx'), project(':sechub-wrapper-prepare'), - - + + project(':sechub-wrapper-secret-validator'), + + /* archUnit */ + project(':sechub-archunit-test') ], /* adapter projects - have simple spring dependencies, but know only sechub-adapter as base */ @@ -59,7 +68,7 @@ projectType = [ project(':deprecated-sechub-adapter-netsparker'), project(':deprecated-sechub-adapter-nessus'), ], - + /* PDS projects, all contained inside product delegation server */ springBootPDSProjects: [ project(':sechub-pds'), @@ -87,15 +96,19 @@ projectType = [ project(':sechub-statistic'), ], - + bootableSpringApplicationProjects:[ project(':sechub-server'), project(':sechub-pds'), project(':sechub-wrapper-checkmarx'), project(':sechub-wrapper-prepare'), + project(':sechub-webui'), + project(':sechub-wrapper-secret-validator'), + ], + + springBootWebUIProjects:[ + project('sechub-webui') ], - - springBootWebUIProjects:[], // is filled later programmatically /* documentation projects */ springDocProjects: [ @@ -118,38 +131,23 @@ projectType = [ asciiDoctorProjects: [ project(':sechub-doc'), ], - + + archUnitProjects: [ + // will be filled automatically in code below + ], + noSpotless : [ - + project('sechub-examples:example-sechub-api-java'), + project('sechub-openapi-java-client') ], - + integrationTestProjects: [ project(':sechub-integrationtest'), + project(':sechub-systemtest'), ], ] } -if (secHubBuildStage.providesGeneratedOpenApiFile()){ - /* add the java projects which need an open api file / compiled java api */ - projectType.javaProjects.add(project(':sechub-api-java')) - - projectType.javaProjects.add(project(':sechub-systemtest')) - projectType.javaProjects.add(project(':sechub-pds-tools')) - - projectType.javaProjects.add(project(':sechub-examples:example-sechub-api-java')) - - /* avoid spotless duplication problem in gradle build */ - projectType.noSpotless.add(project(':sechub-examples:example-sechub-api-java')) - - /* make it possible to use integration test parts for systemtest (unit tests) as well */ - projectType.integrationTestProjects.add(project(':sechub-systemtest')) - - /* webui */ - projectType.bootableSpringApplicationProjects.add(project(':sechub-webui')) - projectType.springBootWebUIProjects.add(project(':sechub-webui')) -} - - /* dynamically define java projects */ projectType.springBootProjects.addAll(projectType.springBootSecHubServerProjects) projectType.springBootProjects.addAll(projectType.springBootPDSProjects) @@ -162,3 +160,7 @@ projectType.javaProjects.addAll(projectType.springBootProjects) /* dynamically add all java projects as eclipse projects:*/ projectType.eclipseProjects.addAll(projectType.javaProjects) projectType.eclipseProjects.addAll(projectType.goProjects) + +/* archUnit */ +projectType.archUnitProjects.addAll(projectType.javaProjects) +projectType.archUnitProjects.remove(project(':sechub-archunit-test')) diff --git a/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/CheckmarxResilienceConsultant.java b/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/CheckmarxResilienceConsultant.java index 19a22a510f..0916609451 100644 --- a/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/CheckmarxResilienceConsultant.java +++ b/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/CheckmarxResilienceConsultant.java @@ -57,7 +57,7 @@ public ResilienceProposal consultFor(ResilienceContext context) { if (rootCause instanceof HttpClientErrorException) { HttpClientErrorException hce = (HttpClientErrorException) rootCause; - int statusCode = hce.getRawStatusCode(); + int statusCode = hce.getStatusCode().value(); if (statusCode == 400) { /* * BAD request - this can happen for same project scans put to queue because diff --git a/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/support/CheckmarxProjectSupport.java b/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/support/CheckmarxProjectSupport.java index ba6fa724d4..f18ab4128b 100644 --- a/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/support/CheckmarxProjectSupport.java +++ b/sechub-adapter-checkmarx/src/main/java/com/mercedesbenz/sechub/adapter/checkmarx/support/CheckmarxProjectSupport.java @@ -52,7 +52,7 @@ public void ensureProjectExists(CheckmarxContext context) throws AdapterExceptio context.setNewProject(false); return; } catch (HttpStatusCodeException e) { - if (e.getRawStatusCode() != 404) { + if (e.getStatusCode().value() != 404) { /* only 404 - not found is accepted */ throw context.asAdapterException( CheckmarxAdapter.CHECKMARX_MESSAGE_PREFIX + "HTTP status=" + e.getStatusCode() + " (expected was only 404 for non existing project)", diff --git a/sechub-scan-product-checkmarx/src/test/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxResilienceCallbackTest.java b/sechub-adapter-checkmarx/src/test/java/com/mercedesbenz/sechub/adapter/checkmarx/CheckmarxResilienceCallbackTest.java similarity index 91% rename from sechub-scan-product-checkmarx/src/test/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxResilienceCallbackTest.java rename to sechub-adapter-checkmarx/src/test/java/com/mercedesbenz/sechub/adapter/checkmarx/CheckmarxResilienceCallbackTest.java index 937100b541..f7df33e5ee 100644 --- a/sechub-scan-product-checkmarx/src/test/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxResilienceCallbackTest.java +++ b/sechub-adapter-checkmarx/src/test/java/com/mercedesbenz/sechub/adapter/checkmarx/CheckmarxResilienceCallbackTest.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan.product.checkmarx; +package com.mercedesbenz.sechub.adapter.checkmarx; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; @@ -10,9 +10,6 @@ import com.mercedesbenz.sechub.adapter.AdapterMetaData; import com.mercedesbenz.sechub.adapter.AdapterMetaDataCallback; -import com.mercedesbenz.sechub.adapter.checkmarx.CheckmarxMetaDataID; -import com.mercedesbenz.sechub.adapter.checkmarx.CheckmarxResilienceCallback; -import com.mercedesbenz.sechub.adapter.checkmarx.CheckmarxResilienceConsultant; import com.mercedesbenz.sechub.commons.core.resilience.ResilienceContext; public class CheckmarxResilienceCallbackTest { diff --git a/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java b/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java index 728f1a29ef..1f88d03cee 100644 --- a/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java +++ b/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java @@ -169,12 +169,15 @@ private void waitForJobDone(PDSContext context) throws Exception { /* see PDSJobStatusState.java */ jobstatus = context.getResilientJobStatusResultExecutor().executeResilient(() -> getJobStatus(context)); - PDSJobStatusState state = jobstatus.state; + PDSJobStatusState state = jobstatus.getState(); switch (state) { case DONE: jobEnded = true; break; case FAILED: + if (jobstatus.isEncryptionOutOfSync()) { + throw new PDSEncryptionOutOfSyncException(); + } throw asAdapterException("PDS job execution failed: TimeOut=" + timeOutCheck.wasTimeOut() + ",JobEnded=" + jobEnded, config); case CANCELED: case CANCEL_REQUESTED: @@ -454,7 +457,7 @@ private AdapterExecutionResult handleExecutionTypeRestart(PDSContext context, Ad /* check job status */ try { PDSJobStatus currentPdsJobStatus = getJobStatus(context, UUID.fromString(pdsJobUUID)); - currentPdsJobState = currentPdsJobStatus.state; + currentPdsJobState = currentPdsJobStatus.getState(); if (currentPdsJobState == null) { throw new IllegalStateException("PDS job state null is not supported!"); diff --git a/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSEncryptionOutOfSyncException.java b/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSEncryptionOutOfSyncException.java new file mode 100644 index 0000000000..30cb2d6a15 --- /dev/null +++ b/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSEncryptionOutOfSyncException.java @@ -0,0 +1,7 @@ +package com.mercedesbenz.sechub.adapter.pds; + +public class PDSEncryptionOutOfSyncException extends Exception { + + private static final long serialVersionUID = 1L; + +} diff --git a/sechub-adapter-pds/src/test/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1Test.java b/sechub-adapter-pds/src/test/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1Test.java index 57b2c402ff..cac27e620a 100644 --- a/sechub-adapter-pds/src/test/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1Test.java +++ b/sechub-adapter-pds/src/test/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1Test.java @@ -249,7 +249,7 @@ private void preparePDSJobCreation(UUID pdsJobUUID) throws AdapterException { private void preparePDSJobStatus(UUID pdsJobUUID, PDSJobStatusState state) { PDSJobStatus jobStatus = new PDSJobStatus(); - jobStatus.state = state; + jobStatus.setState(state); ResponseEntity responseEntity = new ResponseEntity<>(jobStatus, HttpStatus.OK); when(restOperations.getForEntity(eq("null/api/job/" + pdsJobUUID.toString() + "/status"), eq(PDSJobStatus.class))).thenReturn(responseEntity); } diff --git a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallback.java b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallback.java index 7bb4343911..506978825e 100644 --- a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallback.java +++ b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallback.java @@ -31,7 +31,7 @@ public FileBasedAdapterMetaDataCallback(File file) { public void persist(AdapterMetaData metaData) { String metaDataJson = JSONConverter.get().toJSON(metaData); try { - writer.save(file, metaDataJson, true); + writer.writeTextToFile(file, metaDataJson, true); } catch (IOException e) { throw new IllegalStateException("Was not able to store meta data!", e); } @@ -45,7 +45,7 @@ public AdapterMetaData getMetaDataOrNull() { } try { - String data = reader.loadTextFile(file); + String data = reader.readTextFromFile(file); if (data == null || data.isEmpty()) { return null; } diff --git a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallbackTest.java b/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallbackTest.java index 70c3866450..962bd2b0e7 100644 --- a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallbackTest.java +++ b/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/FileBasedAdapterMetaDataCallbackTest.java @@ -59,7 +59,7 @@ void reading_an_empty_file_does_return_null() throws Exception { void reading_a_clean_json_does_return_empty_metadata_object() throws Exception { /* prepare */ TestFileWriter writer = new TestFileWriter(); - writer.save(testFile, "{}", true); + writer.writeTextToFile(testFile, "{}", true); /* execute */ AdapterMetaData metaData = callbackToTest.getMetaDataOrNull(); diff --git a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/APIURLSupportTest.java b/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/support/APIURLSupportTest.java similarity index 95% rename from sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/APIURLSupportTest.java rename to sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/support/APIURLSupportTest.java index 1121a82ecb..cbc22e11e5 100644 --- a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/APIURLSupportTest.java +++ b/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/support/APIURLSupportTest.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.adapter; +package com.mercedesbenz.sechub.adapter.support; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -10,7 +10,7 @@ import org.junit.Before; import org.junit.Test; -import com.mercedesbenz.sechub.adapter.support.APIURLSupport; +import com.mercedesbenz.sechub.adapter.AdapterConfig; public class APIURLSupportTest { private APIURLSupport supportToTest; diff --git a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/JSONAdapterSupportTest.java b/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/support/JSONAdapterSupportTest.java similarity index 94% rename from sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/JSONAdapterSupportTest.java rename to sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/support/JSONAdapterSupportTest.java index fd800db8be..5a99d2b13b 100644 --- a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/JSONAdapterSupportTest.java +++ b/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/support/JSONAdapterSupportTest.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.adapter; +package com.mercedesbenz.sechub.adapter.support; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; @@ -17,7 +17,10 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.matching.StringValuePattern; -import com.mercedesbenz.sechub.adapter.support.JSONAdapterSupport; +import com.mercedesbenz.sechub.adapter.Adapter; +import com.mercedesbenz.sechub.adapter.AdapterException; +import com.mercedesbenz.sechub.adapter.AdapterLogId; +import com.mercedesbenz.sechub.adapter.TraceIdProvider; import com.mercedesbenz.sechub.test.junit4.ExpectedExceptionFactory; public class JSONAdapterSupportTest { diff --git a/sechub-administration/build.gradle b/sechub-administration/build.gradle index d070c4f68b..5f084d5c08 100644 --- a/sechub-administration/build.gradle +++ b/sechub-administration/build.gradle @@ -7,6 +7,7 @@ */ dependencies { implementation project(':sechub-shared-kernel') + implementation project(':sechub-commons-encryption') // necessary to validate implementation library.springboot_starter_thymeleaf testImplementation project(':sechub-testframework') diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/APITokenGenerator.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/APITokenGenerator.java index 59456d57cc..7d7fc2bb6e 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/APITokenGenerator.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/APITokenGenerator.java @@ -4,9 +4,9 @@ import java.util.Base64; import java.util.UUID; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service +@Component public class APITokenGenerator { public String generateNewAPIToken() { diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java index 78defbfc1c..ed8a2aab44 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java @@ -106,6 +106,12 @@ private AdministrationAPIConstants() { public static final String API_CHANGE_PROJECT_ACCESSLEVEL = API_ADMINISTRATION + "project/{projectId}/accesslevel/{projectAccessLevel}"; + /* +-----------------------------------------------------------------------+ */ + /* +............................ Encryption................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String API_ADMIN_ENCRYPTION_ROTATION = API_ADMINISTRATION + "encryption/rotate"; + public static final String API_ADMIN_ENCRYPTION_STATUS = API_ADMINISTRATION + "encryption/status"; + /* +-----------------------------------------------------------------------+ */ /* +............................ Anonymous ................................+ */ /* +-----------------------------------------------------------------------+ */ diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/OneTimeTokenGenerator.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/OneTimeTokenGenerator.java index 217e1d02b4..d12901fa3b 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/OneTimeTokenGenerator.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/OneTimeTokenGenerator.java @@ -4,9 +4,9 @@ import java.util.Base64; import java.util.UUID; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; -@Service +@Component public class OneTimeTokenGenerator { public String generateNewOneTimeToken() { diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/AdministrationEncryptionRotationService.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/AdministrationEncryptionRotationService.java new file mode 100644 index 0000000000..10f140b32f --- /dev/null +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/AdministrationEncryptionRotationService.java @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.administration.encryption; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.UserContextService; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionDataValidator; +import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsSendingAsyncMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminStartsEncryptionRotation; + +@Service +public class AdministrationEncryptionRotationService { + + @Autowired + DomainMessageService domainMessageService; + + @Autowired + SecHubEncryptionDataValidator validator; + + @Autowired + AuditLogService auditLogService; + + @Autowired + UserContextService userContextService; + + @UseCaseAdminStartsEncryptionRotation(@Step(number = 2, name = "Service call", description = "Triggers rotation of encryption via domain message")) + @IsSendingAsyncMessage(MessageID.START_ENCRYPTION_ROTATION) + public void rotateEncryption(SecHubEncryptionData data) { + if (data == null) { + throw new IllegalArgumentException("data may not be null!"); + } + auditLogService.log("started encryption rotation. New cipher algorithm will be: {}, datasource type:{}, datasource: {}", data.getAlgorithm(), + data.getPasswordSourceType(), data.getPasswordSourceData()); + + String executedBy = userContextService.getUserId(); + + DomainMessage message = new DomainMessage(MessageID.START_ENCRYPTION_ROTATION); + message.set(MessageDataKeys.SECHUB_ENCRYPT_ROTATION_DATA, data); + message.set(MessageDataKeys.EXECUTED_BY, executedBy); + + domainMessageService.sendAsynchron(message); + } + +} diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/AdministrationEncryptionStatusService.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/AdministrationEncryptionStatusService.java new file mode 100644 index 0000000000..3e9dbf8486 --- /dev/null +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/AdministrationEncryptionStatusService.java @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.administration.encryption; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageSynchronousResult; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsSendingSyncMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminFetchesEncryptionStatus; + +@Service +public class AdministrationEncryptionStatusService { + + @Autowired + DomainMessageService domainMessageService; + + @Autowired + AuditLogService auditLogService; + + @UseCaseAdminFetchesEncryptionStatus(@Step(number = 1, name = "Service call", description = "Services collects encryption status from domains via event bus")) + public SecHubEncryptionStatus fetchStatus() { + auditLogService.log("starts collecting encryption status"); + + SecHubEncryptionStatus sechubEncryptionStatus = new SecHubEncryptionStatus(); + collectScheduleEncryptionStatus(sechubEncryptionStatus); + + return sechubEncryptionStatus; + + } + + @IsSendingSyncMessage(MessageID.GET_ENCRYPTION_STATUS_SCHEDULE_DOMAIN) + private void collectScheduleEncryptionStatus(SecHubEncryptionStatus status) { + DomainMessage message = new DomainMessage(MessageID.GET_ENCRYPTION_STATUS_SCHEDULE_DOMAIN); + + DomainMessageSynchronousResult result = domainMessageService.sendSynchron(message); + SecHubDomainEncryptionStatus schedulerStatus = result.get(MessageDataKeys.SECHUB_DOMAIN_ENCRYPTION_STATUS); + + status.getDomains().add(schedulerStatus); + } + +} diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/EncryptionAdministrationRestController.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/EncryptionAdministrationRestController.java new file mode 100644 index 0000000000..b7cd81ebb7 --- /dev/null +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/encryption/EncryptionAdministrationRestController.java @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.administration.encryption; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.MediaType; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.mercedesbenz.sechub.domain.administration.AdministrationAPIConstants; +import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionDataValidator; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminFetchesEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminStartsEncryptionRotation; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.validation.Valid; + +/** + * The rest api for encryption done by a super admin. + * + * @author Albert Tregnaghi + * + */ +@RestController +@EnableAutoConfiguration +@RolesAllowed(RoleConstants.ROLE_SUPERADMIN) +@Profile({ Profiles.TEST, Profiles.ADMIN_ACCESS }) +public class EncryptionAdministrationRestController { + + @Autowired + AdministrationEncryptionRotationService administrationEncryptionRotationService; + + @Autowired + AdministrationEncryptionStatusService administrationStatusService; + + @Autowired + SecHubEncryptionDataValidator encryptionDataValidator; + + /* @formatter:off */ + @UseCaseAdminStartsEncryptionRotation(@Step(number=1,name="Rest call",description="Admin triggers rotation of encryption via REST", needsRestDoc =true)) + @RequestMapping(path = AdministrationAPIConstants.API_ADMIN_ENCRYPTION_ROTATION, method = RequestMethod.POST, produces= {MediaType.APPLICATION_JSON_VALUE}) + public void rotateEncryption(@RequestBody @Valid SecHubEncryptionData data) { + /* @formatter:on */ + administrationEncryptionRotationService.rotateEncryption(data); + } + + /* @formatter:off */ + @UseCaseAdminFetchesEncryptionStatus(@Step(number=1,name="Rest call",description="Admin fetches encryption status from domains via REST", needsRestDoc =true)) + @RequestMapping(path = AdministrationAPIConstants.API_ADMIN_ENCRYPTION_STATUS, method = RequestMethod.GET, produces= {MediaType.APPLICATION_JSON_VALUE}) + public SecHubEncryptionStatus fetchEncryptionStatus() { + /* @formatter:on */ + return administrationStatusService.fetchStatus(); + } + + @InitBinder + protected void initBinder(WebDataBinder binder) { + binder.setValidator(encryptionDataValidator); + } + +} diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformation.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformation.java index 63c3743c9f..dc34c95f03 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformation.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformation.java @@ -44,11 +44,6 @@ public class JobInformation { * This is a free text field and shows up some information about */ public static final String COLUMN_INFO = "INFO"; - /** - * JSON configuration for this job. Interesting for administrators because of - * support - */ - public static final String COLUMN_CONFIGURATION = "CONFIGURATION"; /* +-----------------------------------------------------------------------+ */ /* +............................ JPQL .....................................+ */ @@ -61,7 +56,6 @@ public class JobInformation { public static final String PROPERTY_PROJECT_ID = "projectId"; public static final String PROPERTY_OWNER = "owner"; public static final String PROPERTY_SINCE = "since"; - public static final String PROPERTY_CONFIGURATION = "configuration"; public static final String QUERY_FIND_ALL_RUNNING_JOBS = "SELECT j FROM JobInformation j where j.status = com.mercedesbenz.sechub.domain.administration.job.JobStatus.RUNNING"; public static final String QUERY_DELETE_JOBINFORMATION_FOR_JOBUUID = "DELETE FROM JobInformation j WHERE j.jobUUID=:jobUUID"; @@ -95,9 +89,6 @@ public JobInformation(UUID jobUUID) { @Column(name = COLUMN_SINCE) // remark: we setup hibernate to use UTC settings - see application.properties LocalDateTime since; - @Column(name = COLUMN_CONFIGURATION) - String configuration; - @Column(name = COLUMN_INFO) String info; @@ -113,14 +104,6 @@ public void setOwner(String owner) { this.owner = owner; } - public String getConfiguration() { - return configuration; - } - - public void setConfiguration(String jsonConfiguration) { - this.configuration = jsonConfiguration; - } - public void setProjectId(String projectId) { this.projectId = projectId; } diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateService.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateService.java index 0277fa97ea..9d314f2fa2 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateService.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateService.java @@ -37,26 +37,25 @@ public void createByMessage(JobMessage message, JobStatus status) { LOG.debug("Creating a new job information entry for project: {}, SecHub job: {}", projectId, jobUUID); - JobInformation entity = null; + JobInformation jobInformation = null; Optional existingInfo = repository.findById(jobUUID); if (existingInfo.isPresent()) { - entity = existingInfo.get(); + jobInformation = existingInfo.get(); LOG.warn("There was an existing information entity about SecHub job: {}. The entity will be reused and updated.", jobUUID); } else { - entity = new JobInformation(jobUUID); + jobInformation = new JobInformation(jobUUID); } - entity.setProjectId(projectId); - entity.setConfiguration(message.getConfiguration()); - entity.setOwner(message.getOwner()); - entity.setSince(message.getSince()); + jobInformation.setProjectId(projectId); + jobInformation.setOwner(message.getOwner()); + jobInformation.setSince(message.getSince()); - entity.setStatus(status); + jobInformation.setStatus(status); - entity = repository.save(entity); + jobInformation = repository.save(jobInformation); LOG.debug("Saved job information entry for project: {}, SecHub job: {}, ", projectId, jobUUID); } diff --git a/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateServiceTest.java b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateServiceTest.java index 353b9844a6..336c9d3df1 100644 --- a/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateServiceTest.java +++ b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/job/JobInformationCreateServiceTest.java @@ -39,7 +39,6 @@ void beforeEach() { @Test void createByMessage_no_existing_entity_new_entry_will_be_created_and_saved() { /* prepare */ - String configuration = "{ dummy }"; when(repository.findById(jobUUID)).thenReturn(Optional.empty()); @@ -50,7 +49,6 @@ void createByMessage_no_existing_entity_new_entry_will_be_created_and_saved() { message.setOwner("newOwner"); message.setProjectId("newProjectId"); message.setSince(since); - message.setConfiguration(configuration); JobStatus status = JobStatus.RUNNING; @@ -69,7 +67,6 @@ void createByMessage_no_existing_entity_new_entry_will_be_created_and_saved() { assertEquals("newOwner", savedJobInformation.getOwner()); assertEquals("newProjectId", savedJobInformation.getProjectId()); assertEquals(since, savedJobInformation.getSince()); - assertEquals(configuration, savedJobInformation.getConfiguration()); assertEquals(null, savedJobInformation.version); assertEquals(status, savedJobInformation.getStatus()); @@ -93,7 +90,6 @@ void createByMessage_an_existing_entity_entry_will_be_reused_and_saved() { message.setOwner("newOwner"); message.setProjectId("newProjectId"); message.setSince(since); - message.setConfiguration(configuration); JobStatus status = JobStatus.RUNNING; @@ -113,7 +109,6 @@ void createByMessage_an_existing_entity_entry_will_be_reused_and_saved() { assertEquals("newOwner", savedJobInformation.getOwner()); assertEquals("newProjectId", savedJobInformation.getProjectId()); assertEquals(since, savedJobInformation.getSince()); - assertEquals(configuration, savedJobInformation.getConfiguration()); assertEquals(42, savedJobInformation.version.intValue()); assertEquals(status, savedJobInformation.getStatus()); diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/cli/SecHubAnalyzerApplication.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/cli/SecHubAnalyzerApplication.java index 7ff4aaa85d..7f3deca8b6 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/cli/SecHubAnalyzerApplication.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/cli/SecHubAnalyzerApplication.java @@ -12,8 +12,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.mercedesbenz.analyzer.model.AnalyzerResult; import com.mercedesbenz.sechub.analyzer.core.Analyzer; +import com.mercedesbenz.sechub.analyzer.model.AnalyzerResult; import ch.qos.logback.classic.Level; diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/Analyzer.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/Analyzer.java index 19291b768b..626a474423 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/Analyzer.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/Analyzer.java @@ -14,8 +14,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.mercedesbenz.analyzer.model.AnalyzerResult; -import com.mercedesbenz.analyzer.model.MarkerPair; +import com.mercedesbenz.sechub.analyzer.model.AnalyzerResult; +import com.mercedesbenz.sechub.analyzer.model.MarkerPair; /** * Main analyzer class which represents the entry point for callers. Starts diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/FileProcessor.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/FileProcessor.java index 590ceb40d1..85c27ec891 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/FileProcessor.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/core/FileProcessor.java @@ -9,9 +9,9 @@ import java.util.LinkedList; import java.util.List; -import com.mercedesbenz.analyzer.model.Marker; -import com.mercedesbenz.analyzer.model.MarkerPair; -import com.mercedesbenz.analyzer.model.MarkerType; +import com.mercedesbenz.sechub.analyzer.model.Marker; +import com.mercedesbenz.sechub.analyzer.model.MarkerPair; +import com.mercedesbenz.sechub.analyzer.model.MarkerType; /** * Searches through a file looking for SecHub markers diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/AnalyzerResult.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/AnalyzerResult.java similarity index 98% rename from sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/AnalyzerResult.java rename to sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/AnalyzerResult.java index fdfac6cc19..3ccb816598 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/AnalyzerResult.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/AnalyzerResult.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.analyzer.model; +package com.mercedesbenz.sechub.analyzer.model; import java.io.IOException; import java.util.HashMap; diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/DeepClonable.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/DeepClonable.java similarity index 66% rename from sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/DeepClonable.java rename to sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/DeepClonable.java index 3f117d25d9..4d89c559a7 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/DeepClonable.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/DeepClonable.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.analyzer.model; +package com.mercedesbenz.sechub.analyzer.model; public interface DeepClonable { public T deepClone(); diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/Marker.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/Marker.java similarity index 97% rename from sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/Marker.java rename to sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/Marker.java index e275143cc4..d9d45c542d 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/Marker.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/Marker.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.analyzer.model; +package com.mercedesbenz.sechub.analyzer.model; public class Marker implements DeepClonable { diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/MarkerPair.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/MarkerPair.java similarity index 97% rename from sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/MarkerPair.java rename to sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/MarkerPair.java index 53815c91c0..670d189c6b 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/MarkerPair.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/MarkerPair.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.analyzer.model; +package com.mercedesbenz.sechub.analyzer.model; /** * Represents always a pair of markers containing diff --git a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/MarkerType.java b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/MarkerType.java similarity index 62% rename from sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/MarkerType.java rename to sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/MarkerType.java index ec6f222649..69d48cbe44 100644 --- a/sechub-analyzer-cli/src/main/java/com/mercedesbenz/analyzer/model/MarkerType.java +++ b/sechub-analyzer-cli/src/main/java/com/mercedesbenz/sechub/analyzer/model/MarkerType.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.analyzer.model; +package com.mercedesbenz.sechub.analyzer.model; public enum MarkerType { diff --git a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/AnalyzerTest.java b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/AnalyzerTest.java index 9931acf6a3..b91920fed6 100644 --- a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/AnalyzerTest.java +++ b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/AnalyzerTest.java @@ -17,10 +17,10 @@ import org.junit.Before; import org.junit.Test; -import com.mercedesbenz.analyzer.model.AnalyzerResult; -import com.mercedesbenz.analyzer.model.Marker; -import com.mercedesbenz.analyzer.model.MarkerPair; -import com.mercedesbenz.analyzer.model.MarkerType; +import com.mercedesbenz.sechub.analyzer.model.AnalyzerResult; +import com.mercedesbenz.sechub.analyzer.model.Marker; +import com.mercedesbenz.sechub.analyzer.model.MarkerPair; +import com.mercedesbenz.sechub.analyzer.model.MarkerType; /** * Integration Tests diff --git a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/FileProcessorTest.java b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/FileProcessorTest.java index 920c413ec5..1a62fbdaf7 100644 --- a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/FileProcessorTest.java +++ b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/core/FileProcessorTest.java @@ -14,9 +14,9 @@ import org.junit.Before; import org.junit.Test; -import com.mercedesbenz.analyzer.model.Marker; -import com.mercedesbenz.analyzer.model.MarkerPair; -import com.mercedesbenz.analyzer.model.MarkerType; +import com.mercedesbenz.sechub.analyzer.model.Marker; +import com.mercedesbenz.sechub.analyzer.model.MarkerPair; +import com.mercedesbenz.sechub.analyzer.model.MarkerType; public class FileProcessorTest { diff --git a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/AnalyzerResultTest.java b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/AnalyzerResultTest.java index af2478706d..b677c7a502 100644 --- a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/AnalyzerResultTest.java +++ b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/AnalyzerResultTest.java @@ -11,11 +11,6 @@ import org.junit.Test; -import com.mercedesbenz.analyzer.model.AnalyzerResult; -import com.mercedesbenz.analyzer.model.Marker; -import com.mercedesbenz.analyzer.model.MarkerPair; -import com.mercedesbenz.analyzer.model.MarkerType; - public class AnalyzerResultTest { @Test diff --git a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerPairTest.java b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerPairTest.java index e92d7f7d21..db07d2a10e 100644 --- a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerPairTest.java +++ b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerPairTest.java @@ -6,10 +6,6 @@ import org.junit.Test; -import com.mercedesbenz.analyzer.model.Marker; -import com.mercedesbenz.analyzer.model.MarkerPair; -import com.mercedesbenz.analyzer.model.MarkerType; - public class MarkerPairTest { @Test public void test_deepClone() { diff --git a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerTest.java b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerTest.java index 14913e4611..c8cc7fd3c8 100644 --- a/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerTest.java +++ b/sechub-analyzer-cli/src/test/java/com/mercedesbenz/sechub/analyzer/model/MarkerTest.java @@ -6,9 +6,6 @@ import org.junit.Test; -import com.mercedesbenz.analyzer.model.Marker; -import com.mercedesbenz.analyzer.model.MarkerType; - public class MarkerTest { @Test public void test_deepClone() { diff --git a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java index b94fad9414..63b5b08ef9 100644 --- a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java +++ b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java @@ -98,7 +98,7 @@ private void generateAbstractModel(MapGenInfo info) throws Exception { template.addLine(" }"); template.addLine("}"); - context.getTextFileWriter().save(genFile, template.getCode(), true); + context.getTextFileWriter().writeTextToFile(genFile, template.getCode(), true); } diff --git a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java index 6bdd03a235..b7dc6e885f 100644 --- a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java +++ b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java @@ -110,7 +110,7 @@ private void generatetPublicModel(MapGenInfo info, boolean overwritePublicModelF template.addLine(""); template.addLine("}"); - context.getTextFileWriter().save(genFile, template.getCode(), overwritePublicModelFiles); + context.getTextFileWriter().writeTextToFile(genFile, template.getCode(), overwritePublicModelFiles); } diff --git a/sechub-archunit-test/build.gradle b/sechub-archunit-test/build.gradle new file mode 100644 index 0000000000..4b42b86c17 --- /dev/null +++ b/sechub-archunit-test/build.gradle @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +/*============================================================================ +* Build file for subproject +* +* Root build file: "${rootProject.projectDir}/build.gradle" +* ============================================================================ +*/ + +dependencies { + testImplementation library.springframework_web + + for (project in projectType.archUnitProjects) { + testImplementation project + } + + testImplementation spring_boot_dependency.junit_jupiter + testImplementation library.arch_unit +} + +// executing archunit tests: ./gradlew sechub-archunit-test:test diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitImportOptions.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitImportOptions.java new file mode 100644 index 0000000000..88ade4276c --- /dev/null +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitImportOptions.java @@ -0,0 +1,169 @@ +package mercedesbenz.com.sechub.archunit; + +import java.util.List; + +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.core.importer.Location; + +public class ArchUnitImportOptions { + + public static final String SECHUB_ROOT_FOLDER = "../../sechub/"; + + static List ignoreBuildFolders = new ArchUnitRuntimeSupport().createImportOptionsForBuildSystem(); + + /* Ignore specific packages */ + static ImportOption ignoreAllTests = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/test/"); // ignore any URI to sources that contains '/test/' + } + }; + + static ImportOption ignoreSechubOpenAPIJava = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-openapi-java(*)/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreSechubTestframework = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-testframework/"); // ignore any URI to sources that contains '/sechub-testframework/' + } + }; + + static ImportOption ignoreSharedkernelTest = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("com/mercedesbenz/sechub/sharedkernel/test"); // ignore any URI to sources that contains '/sechub-shared-kernel/' + } + }; + + static ImportOption ignoreIntegrationTest = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-integrationtest/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreDocGen = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/docgen/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreDevelopertools = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/developertools/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreTools = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/tools/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreBuildSrc = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/buildSrc/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreExamples = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-examples/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreNessusAdapter = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/deprecated-sechub-adapter-nessus/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreNessusProduct = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/deprecated-sechub-scan-product-nessus/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreNetsparkerAdapter = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/deprecated-sechub-adapter-netsparker/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreNetsparkerProduct = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/deprecated-sechub-scan-product-netsparker/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreAnalyzerCLI = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-analyzer-cli/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreSechubApiJava = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-api-java/"); // ignore any URI to sources that contains '/openapi/' + } + }; + + static ImportOption ignoreSechubTest = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-test/"); // ignore any URI to sources that contains '/sechub-test/' + } + }; + + static ImportOption ignoreSystemTest = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/sechub-systemtest/"); // ignore any URI to sources that contains '/sechub-systemtest/' + } + }; + + static ImportOption ignoreGenApi = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("/api/internal/gen/"); // ignore any URI to sources that contains '/sechub-gen-api/' + } + }; + + /* Ignore specific classes */ + static ImportOption ignoreProductIdentifierClass = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("sharedkernel/ProductIdentifier"); // ignore any URI to sources that contains '/ProductIdentifier' + } + }; + + static ImportOption ignoreIntegrationTestClass = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("IntegrationTest"); // ignore any URI to sources that contains '/ProductResult' + } + }; + + static ImportOption ignoreSchedulerSourcecodeUploadService = new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains("SchedulerSourcecodeUploadService"); // ignore any URI to sources that contains '/ProductResult' + } + }; +} diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitRuntimeSupport.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitRuntimeSupport.java new file mode 100644 index 0000000000..08a1774a08 --- /dev/null +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitRuntimeSupport.java @@ -0,0 +1,59 @@ +package mercedesbenz.com.sechub.archunit; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.core.importer.Location; + +/* + * This class provides build system specific import options for ArchUnit. + * Depending on the build system, the binary folder is different for gradle, intelliJ or eclipse. + * The property "sechub.archunit.buildsystem" can be set to "gradle", "intelliJ" or "eclipse" and is by default gradle + */ +class ArchUnitRuntimeSupport { + + /* @formatter:off */ + private Map buildSystemToBinaryFolder = Map.of( + "gradle", "/build/classes/", + "intelliJ", "/out/", + "eclipse", "/bin/"); + /* @formatter:on */ + + public List createImportOptionsForBuildSystem() { + String buildSystem = resolveBuildSystem(); + + Map binaryFolderToIgnoreMap = new HashMap<>(buildSystemToBinaryFolder); + binaryFolderToIgnoreMap.remove(buildSystem); + + List importOptions = new ArrayList<>(); + for (String binaryFolder : binaryFolderToIgnoreMap.values()) { + importOptions.add(new ImportOption() { + @Override + public boolean includes(Location location) { + return !location.contains(binaryFolder); + } + }); + } + + return importOptions; + } + + private String resolveBuildSystem() { + String buildSystem = System.getProperty("sechub.archunit.buildsystem"); + if (buildSystem == null || buildSystem.isBlank()) { + buildSystem = "gradle"; + } + + switch (buildSystem) { + case "gradle": + case "intelliJ": + case "eclipse": + return buildSystem; + default: + throw new IllegalStateException("Unsupported build system: " + buildSystem); + } + } +} diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/CodingRulesTest.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/CodingRulesTest.java new file mode 100644 index 0000000000..b1019cedd3 --- /dev/null +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/CodingRulesTest.java @@ -0,0 +1,148 @@ +package mercedesbenz.com.sechub.archunit; + +import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith; +import static com.tngtech.archunit.lang.conditions.ArchConditions.accessTargetWhere; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import static com.tngtech.archunit.library.GeneralCodingRules.*; +import static mercedesbenz.com.sechub.archunit.ArchUnitImportOptions.*; + +import org.junit.jupiter.api.Test; + +import com.tngtech.archunit.core.domain.JavaAccess; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; + +@AnalyzeClasses +public class CodingRulesTest { + + private JavaClasses importedClasses; + + @Test + void classes_should_not_throw_generic_exceptions() { + /* prepare */ + ignoreTestGeneratedAndDeprecatedPackages(); + + /* execute + test */ + NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS.check(importedClasses); + } + + @Test + void classes_should_not_use_deprecated_members() { + /* prepare */ + /* @formatter:off */ + importedClasses = new ClassFileImporter() + .withImportOptions(ignoreBuildFolders) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .withImportOption(ignoreAllTests) + .withImportOption(ignoreSechubOpenAPIJava) + .withImportOption(ignoreNessusAdapter) + .withImportOption(ignoreNessusProduct) + .withImportOption(ignoreNetsparkerAdapter) + .withImportOption(ignoreNetsparkerProduct) + .withImportOption(ignoreIntegrationTest) + .withImportOption(ignoreSechubApiJava) + .withImportOption(ignoreProductIdentifierClass) + .withImportOption(ignoreIntegrationTestClass) + .withImportOption(ignoreDevelopertools) + .withImportOption(ignoreSchedulerSourcecodeUploadService) + .withImportOption(ignoreSystemTest) + .importPath(SECHUB_ROOT_FOLDER); + + /* execute + test */ + /* custom version of DEPRECATED_API_SHOULD_NOT_BE_USED */ + noClasses() + .should(accessTargetWhere(JavaAccess.Predicates.target(annotatedWith(Deprecated.class))) + .as("access @Deprecated members")) +// Following lines were out-commented because of JsonSerialize annotation uses deprecated default implementation +// .orShould(dependOnClassesThat(annotatedWith(Deprecated.class)) +// .as("depend on @Deprecated classes")) + .because("there should be a better alternative") + .check(importedClasses); + /* @formatter:on */ + } + + @Test + void assertion_error_must_have_detailed_message() { + /* prepare */ + ignoreTestGeneratedAndDeprecatedPackages(); + + /* execute + test */ + ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE.check(importedClasses); + } + + @Test + void test_classes_should_be_in_the_same_package_as_implementation() { + /* prepare */ + /* @formatter:off */ + importedClasses = new ClassFileImporter() + .withImportOptions(ignoreBuildFolders) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .withImportOption(ignoreSechubOpenAPIJava) + .withImportOption(ignoreSechubApiJava) + .withImportOption(ignoreDocGen) + .withImportOption(ignoreIntegrationTest) + .withImportOption(ignoreSechubTest) + .withImportOption(ignoreSystemTest) + .withImportOption(ignoreGenApi) + .importPath(SECHUB_ROOT_FOLDER); + + /* execute + test */ + testClassesShouldResideInTheSamePackageAsImplementation().check(importedClasses); + /* @formatter:on */ + } + + @Test + void classes_should_not_use_java_util_logging() { + /* prepare */ + ignoreTestGeneratedAndDeprecatedPackages(); + + /* execute + test */ + NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(importedClasses); + } + + @Test + void classes_should_not_use_standard_streams() { + /* prepare */ + /* @formatter:off */ + importedClasses = new ClassFileImporter() + .withImportOptions(ignoreBuildFolders) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .withImportOption(ignoreAllTests) + .withImportOption(ignoreSechubOpenAPIJava) + .withImportOption(ignoreIntegrationTest) + .withImportOption(ignoreDocGen) + .withImportOption(ignoreDevelopertools) + .withImportOption(ignoreTools) + .withImportOption(ignoreBuildSrc) + .withImportOption(ignoreAnalyzerCLI) + .withImportOption(ignoreExamples) + .importPath(SECHUB_ROOT_FOLDER); + + /* execute + test */ + NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(importedClasses); + /* @formatter:on */ + } + + private void ignoreTestGeneratedAndDeprecatedPackages() { + /* @formatter:off */ + importedClasses = new ClassFileImporter() + .withImportOptions(ignoreBuildFolders) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .withImportOption(ignoreAllTests) + .withImportOption(ignoreSechubOpenAPIJava) + .withImportOption(ignoreNessusAdapter) + .withImportOption(ignoreNessusProduct) + .withImportOption(ignoreNetsparkerAdapter) + .withImportOption(ignoreNetsparkerProduct) + .withImportOption(ignoreIntegrationTest) + .withImportOption(ignoreSechubApiJava) + .withImportOption(ignoreDevelopertools) + .importPath(SECHUB_ROOT_FOLDER); + /* @formatter:on */ + } +} diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/DomainAccessRulesTest.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/DomainAccessRulesTest.java new file mode 100644 index 0000000000..52a8f24993 --- /dev/null +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/DomainAccessRulesTest.java @@ -0,0 +1,88 @@ +package mercedesbenz.com.sechub.archunit; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import static mercedesbenz.com.sechub.archunit.ArchUnitImportOptions.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; + +@AnalyzeClasses +public class DomainAccessRulesTest { + + private static final String DOMAIN_SCAN = "com.mercedesbenz.sechub.domain.scan"; + private static final String DOMAIN_ADMINISTRATION = "com.mercedesbenz.sechub.domain.administration"; + private static final String DOMAIN_STATISTIC = "com.mercedesbenz.sechub.domain.statistic"; + private static final String DOMAIN_SCHEDULE = "com.mercedesbenz.sechub.domain.schedule"; + private static final String DOMAIN_NOTIFICATION = "com.mercedesbenz.sechub.domain.notification"; + private static final String DOMAIN_AUTHORIZATION = "com.mercedesbenz.sechub.domain.authorization"; + + /* @formatter:off */ + private static final List allDomainsToTest = Arrays.asList( + DOMAIN_SCAN, + DOMAIN_ADMINISTRATION, + DOMAIN_STATISTIC, + DOMAIN_SCHEDULE, + DOMAIN_NOTIFICATION, + DOMAIN_AUTHORIZATION + ); + /* @formatter:on */ + + @ParameterizedTest + @ArgumentsSource(DomainDataArgumentProvider.class) + void no_class_in_one_domain_communicate_with_another_domain(String domainToTest) { + /* prepare */ + /* @formatter:off */ + JavaClasses importedClasses = new ClassFileImporter() + .withImportOptions(ignoreBuildFolders) + .withImportOption(ignoreDevelopertools) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .importPath(SECHUB_ROOT_FOLDER); + + /* execute + test */ + noClasses() + .that() + .resideInAPackage(packageIdentifier(domainToTest)) + .should() + .accessClassesThat() + .resideInAnyPackage(resolveOtherDomainsThan(domainToTest)) + .check(importedClasses); + /* @formatter:on */ + } + + private static String[] resolveOtherDomainsThan(String domainToTest) { + List otherDomains = new ArrayList<>(allDomainsToTest); + otherDomains.remove(domainToTest); + String[] otherDomainsArray = new String[otherDomains.size()]; + for (int i = 0; i < otherDomains.size(); i++) { + String packageIdentifier = packageIdentifier(otherDomains.get(i)); + otherDomainsArray[i] = packageIdentifier; + } + return otherDomainsArray; + } + + private static String packageIdentifier(String domain) { + return ".." + domain + ".."; + } + + private static class DomainDataArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return allDomainsToTest.stream().map(domain -> Arguments.of(domain)); + } + } +} diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/NamingConventionTest.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/NamingConventionTest.java new file mode 100644 index 0000000000..cfe381bccf --- /dev/null +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/NamingConventionTest.java @@ -0,0 +1,71 @@ +package mercedesbenz.com.sechub.archunit; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static mercedesbenz.com.sechub.archunit.ArchUnitImportOptions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.stereotype.Service; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; + +@AnalyzeClasses +public class NamingConventionTest { + + private JavaClasses importedClasses; + + @Test + void classes_in_test_packages_containing_test_or_assert_in_name() { + /* prepare */ + /* @formatter:off */ + importedClasses = new ClassFileImporter() + .withImportOptions(ignoreBuildFolders) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .withImportOption(ignoreSechubOpenAPIJava) + .withImportOption(ignoreSechubTestframework) + .withImportOption(ignoreSharedkernelTest) + .withImportOption(ignoreSechubApiJava) + .importPath(SECHUB_ROOT_FOLDER); + + /* execute + test */ + classes() + .that() + .resideInAPackage("..test..") + .should() + .haveSimpleNameContaining("Test") + .orShould() + .haveNameMatching(".*\\.Assert.*") // including inner classes + .orShould() + .haveNameMatching(".*Test\\$.*") // including inner classes + .because("Tests classes should contain 'Test' or 'Assert' in their name.") + .check(importedClasses); + /* @formatter:on */ + } + + @Test + void service_annotated_classes_contain_service_or_executor_in_name() { + /* prepare */ + /* @formatter:off */ + importedClasses = new ClassFileImporter() + .withImportOptions(ignoreBuildFolders) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + .withImportOption(ignoreAllTests) + .withImportOption(ignoreSechubOpenAPIJava) + .importPath("../../sechub/"); + + /* execute + test */ + classes() + .that() + .areAnnotatedWith(Service.class) + .should() + .haveSimpleNameContaining("Service") + .orShould() + .haveSimpleNameContaining("Executor") + .because("Service classes should contain 'Service' or 'Executor' in their name.") + .check(importedClasses); + /* @formatter:on */ + } +} \ No newline at end of file diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/controller.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/controller.go index b72062fdf2..8b3d1cf84e 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/controller.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/controller.go @@ -138,10 +138,10 @@ func prepareScan(context *Context) { sechubUtil.LogError(fmt.Sprintf("%s\nExiting due to fatal error while creating sources zip file...\n", err)) os.Remove(context.sourceZipFileName) // cleanup zip file os.Exit(ExitCodeFailed) + } else if context.sourceZipUploadNeeded { + // calculate checksum for zip file + context.sourceZipFileChecksum = sechubUtil.CreateChecksum(context.sourceZipFileName) } - - // calculate checksum for zip file - context.sourceZipFileChecksum = sechubUtil.CreateChecksum(context.sourceZipFileName) } else { context.sourceZipUploadNeeded = false } @@ -156,10 +156,10 @@ func prepareScan(context *Context) { sechubUtil.LogError(fmt.Sprintf("%s\nExiting due to fatal error while creating binaries tar file...\n", err)) os.Remove(context.binariesTarFileName) // cleanup tar file os.Exit(ExitCodeFailed) + } else if context.binariesTarUploadNeeded { + // calculate checksum for tar file + context.binariesTarFileChecksum = sechubUtil.CreateChecksum(context.binariesTarFileName) } - - // calculate checksum for tar file - context.binariesTarFileChecksum = sechubUtil.CreateChecksum(context.binariesTarFileName) } else { context.binariesTarUploadNeeded = false } diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder.go index 2a0750c9cb..41c2b9c4c6 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder.go @@ -51,6 +51,7 @@ func createBinariesTarFile(context *Context) error { } } } else { + os.Remove(context.binariesTarFileName) context.binariesTarUploadNeeded = false sechubUtil.LogNotice(sechubUtil.TarFileNotCreated) } diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder_test.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder_test.go index 64dd68123f..7278dba0c8 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder_test.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/tarbuilder_test.go @@ -186,4 +186,5 @@ func Test_createBinariesTarFile_HandlesRemoteDataSection(t *testing.T) { /* test */ sechubTestUtil.AssertNoError(err, t) sechubTestUtil.AssertStringContains(string(out), sechubUtil.TarFileNotCreated, t) + sechubTestUtil.AssertFileNotExists(context.binariesTarFileName, t) } diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder.go index ae1882f4b6..0cb36c445e 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder.go @@ -66,6 +66,7 @@ func createSouceCodeZipFile(context *Context) error { } } } else { + os.Remove(context.sourceZipFileName) context.sourceZipUploadNeeded = false sechubUtil.LogNotice(sechubUtil.ZipFileNotCreated) } diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder_test.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder_test.go index 268d72d20a..a4897ca926 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder_test.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/zipbuilder_test.go @@ -181,4 +181,5 @@ func Test_createSouceCodeZipFile_HandleRemoteDataSection(t *testing.T) { /* test */ sechubTestUtil.AssertNoError(err, t) sechubTestUtil.AssertStringContains(string(out), sechubUtil.ZipFileNotCreated, t) + sechubTestUtil.AssertFileNotExists(context.sourceZipFileName, t) } diff --git a/sechub-cli/src/mercedes-benz.com/sechub/testutil/asserts.go b/sechub-cli/src/mercedes-benz.com/sechub/testutil/asserts.go index 651487ed69..7aa97bfa85 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/testutil/asserts.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/testutil/asserts.go @@ -4,6 +4,7 @@ package util import ( "encoding/json" + "errors" "os" "reflect" "strings" @@ -142,13 +143,21 @@ func jsonBytesEqual(a, b []byte) (bool, error) { // AssertFileExists - checks if a file exists func AssertFileExists(filePath string, t *testing.T) { _, err := os.Stat(filePath) - if os.IsNotExist(err) { + if errors.Is(err, os.ErrNotExist) { t.Fatalf("File %q expected, but does not exist.", filePath) } else { Check(err, t) } } +// AssertFileNotExists - checks if a file does not exist +func AssertFileNotExists(filePath string, t *testing.T) { + _, err := os.Stat(filePath) + if ! errors.Is(err, os.ErrNotExist) { + t.Fatalf("File %q exists but it should not.", filePath) + } +} + // AssertMinimalFileSize - checks a file's size to be at least `size` bytes func AssertMinimalFileSize(filePath string, size int64, t *testing.T) { fileinfo, err := os.Stat(filePath) diff --git a/sechub-commons-archive/src/test/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupportTest.java b/sechub-commons-archive/src/test/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupportTest.java index 154f15930b..70db925fed 100644 --- a/sechub-commons-archive/src/test/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupportTest.java +++ b/sechub-commons-archive/src/test/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupportTest.java @@ -284,7 +284,7 @@ void compress_zip_single_data_txt_file_compressed_output_contains_same_content() File targetFile = new File(parentFolder, "sourcecode.zip"); TextFileWriter writer = new TextFileWriter(); - writer.save(textFile, "This is just a test content", true); + writer.writeTextToFile(textFile, "This is just a test content", true); /* execute */ supportToTest.compressFolder(ArchiveType.ZIP, dataFolder, targetFile); @@ -307,7 +307,7 @@ private void assertFileContains(File file, String expectedContent) throws IOExce fail("File:" + file + " does not exist!"); } TextFileReader reader = new TextFileReader(); - String content = reader.loadTextFile(file); + String content = reader.readTextFromFile(file); assertEquals(expectedContent, content); } @@ -324,8 +324,8 @@ void compress_zip_two_txt_files_compressed_output_contains_same_content() throws File targetFile = new File(parentFolder, "sourcecode.zip"); TextFileWriter writer = new TextFileWriter(); - writer.save(textFile1, "text1", true); - writer.save(textFile2, "text2", true); + writer.writeTextToFile(textFile1, "text1", true); + writer.writeTextToFile(textFile2, "text2", true); /* execute */ supportToTest.compressFolder(ArchiveType.ZIP, dataFolder, targetFile); diff --git a/sechub-commons-core/build.gradle b/sechub-commons-core/build.gradle index f373ca021d..c3a3d14c85 100644 --- a/sechub-commons-core/build.gradle +++ b/sechub-commons-core/build.gradle @@ -3,8 +3,6 @@ dependencies { api spring_boot_dependency.slf4j_api - implementation 'org.bouncycastle:bcprov-jdk18on:1.73' - testImplementation spring_boot_dependency.junit_jupiter testImplementation spring_boot_dependency.mockito_core diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/environment/SecureEnvironmentVariableKeyValueRegistry.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/environment/SecureEnvironmentVariableKeyValueRegistry.java index 69382ab9cb..4734308770 100644 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/environment/SecureEnvironmentVariableKeyValueRegistry.java +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/environment/SecureEnvironmentVariableKeyValueRegistry.java @@ -111,7 +111,7 @@ public EnvironmentVariableKeyValueEntry build() { entry.variableName = variableName; if (entry.variableName == null) { - entry.variableName = key.toUpperCase().replace('.', '_'); + entry.variableName = key.toUpperCase().replace('.', '_').replace('-', '_'); } return entry; } diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java deleted file mode 100644 index c94db89fd4..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.util.Arrays; - -/** - * @author Jeremias Eppler - */ -public abstract class AbstractBinaryString implements BinaryString { - byte[] bytes; - - protected AbstractBinaryString(byte[] bytes) { - if (bytes == null) { - throw new IllegalArgumentException("Byte array cannot be null."); - } - - this.bytes = bytes; - } - - protected AbstractBinaryString(String string) { - if (string == null) { - throw new IllegalArgumentException("String cannot be null."); - } - - this.bytes = string.getBytes(); - } - - public abstract String toString(); - - public byte[] getBytes() { - // deep copy - return Arrays.copyOf(bytes, bytes.length); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(bytes); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AbstractBinaryString other = (AbstractBinaryString) obj; - return Arrays.equals(bytes, other.bytes); - } -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java deleted file mode 100644 index 72291d3e03..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -/** - * Providing access to AES-GCM-SIV - * - * AES-GCM-SIV is a nonce misuse-resistant authenticated encryption algorithm. - * - * For more information refer to - * RFC 8452 - * - * @author Jeremias Eppler - */ -public class AesGcmSiv implements PersistenceCipher { - - private SecretKey secret; - private Provider cryptoProvider; - private PersistenceCipherType cipherType; - - private static final String ALGORITHM = "AES/GCM-SIV/NoPadding"; - - /** - * The recommended initialization vector (iv) for AES-GCM-SIV is 12 bytes or 96 - * bits. - * - * For an explanation have a look at: - - * https://datatracker.ietf.org/doc/html/rfc8452#section-4 - - * https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes - */ - public static final int IV_LENGTH_IN_BYTES = 12; - - public static final int AUTHENTICATION_TAG_LENGTH_IN_BITS = 16 * 8; // 16 bytes (128 bits) - - private AesGcmSiv(SecretKey secret, PersistenceCipherType cipherType) { - this.secret = secret; - this.cipherType = cipherType; - cryptoProvider = new BouncyCastleProvider(); - Security.addProvider(cryptoProvider); - } - - public static AesGcmSiv create(BinaryString secret) throws InvalidKeyException { - AesGcmSiv instance = null; - - byte[] rawSecret = secret.getBytes(); - - if (rawSecret.length == 32 || rawSecret.length == 16) { - PersistenceCipherType cipherType = (rawSecret.length == 32) ? PersistenceCipherType.AES_GCM_SIV_256 : PersistenceCipherType.AES_GCM_SIV_128; - SecretKey secretKey = new SecretKeySpec(rawSecret, 0, rawSecret.length, "AES"); - - instance = new AesGcmSiv(secretKey, cipherType); - } else { - throw new InvalidKeyException("The secret has to be 128 or 256 bits long, but was " + (rawSecret.length * 8) + " bits long."); - } - - return instance; - } - - public BinaryString generateNewInitializationVector() { - return generateNewInitializationVector(BinaryStringEncodingType.BASE64); - } - - public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType) { - byte[] initializationVector = new byte[IV_LENGTH_IN_BYTES]; - - SecureRandom random = new SecureRandom(); - random.nextBytes(initializationVector); - - return BinaryStringFactory.createFromBytes(initializationVector, encodingType); - } - - public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException { - return encrypt(plaintext, initializationVector, BinaryStringEncodingType.BASE64); - } - - public String decrypt(BinaryString ciphertext, BinaryString initializationVector) - throws InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { - Cipher cipher; - try { - cipher = Cipher.getInstance(ALGORITHM, cryptoProvider); - - SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES"); - - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes()); - - cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); - - byte[] ciphertextBytes = ciphertext.getBytes(); - - byte[] plaintextBytes = cipher.doFinal(ciphertextBytes); - - String plaintext = new String(plaintextBytes); - - return plaintext; - } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) { - throw new IllegalStateException("Decryption not possible, please check the provider", providerException); - } - } - - @Override - public PersistenceCipherType getCipherType() { - return cipherType; - } - - @Override - public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType) - throws InvalidAlgorithmParameterException, InvalidKeyException { - try { - Cipher cipher = Cipher.getInstance(ALGORITHM, cryptoProvider); - - SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES"); - - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes()); - - cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); - - byte[] ciphertext = cipher.doFinal(plaintext.getBytes()); - - return BinaryStringFactory.createFromBytes(ciphertext, encodingType); - } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) { - throw new IllegalStateException("Encryption not possible, please check the provider", providerException); - } catch (BadPaddingException | IllegalBlockSizeException paddingBlockException) { - throw new IllegalStateException("Should not occure. AES in GCM-SIV mode does not require padding.", paddingBlockException); - } - } -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java deleted file mode 100644 index e957c61467..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.util.Base64; - -/** - * A base64 encoded string. - * - * @author Jeremias Eppler - * - */ -public class Base64String extends AbstractBinaryString { - - Base64String(byte[] bytes) { - super(bytes); - } - - Base64String(String string) { - super(string); - } - - @Override - public String toString() { - return Base64.getEncoder().encodeToString(bytes); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + java.util.Arrays.hashCode(bytes); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Base64String other = (Base64String) obj; - return java.util.Arrays.equals(bytes, other.bytes); - } - - @Override - public BinaryStringEncodingType getType() { - return BinaryStringEncodingType.BASE64; - } -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java deleted file mode 100644 index 94d2dd23d7..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -/** - * A binary string type which knows it's encoding. - * - * The binary string keeps it's internal representation in binary format. In - * addition, the binary string knows it's encoding (e.g. base64 etc.). - * - * @see BinaryStringEncodingType - * - * @author Jeremias Eppler - */ -public interface BinaryString { - public byte[] getBytes(); - - public String toString(); - - public BinaryStringEncodingType getType(); -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java deleted file mode 100644 index 9fb39655b1..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -/** - * Encoding type of a binary string. - * - * @see BinaryString - * - * @author Jeremias Eppler - */ -public enum BinaryStringEncodingType { - /** - * A string with no encoding. - */ - PLAIN, - - /** - * A string encoded in hexadecimal format - */ - HEX, - - /** - * A string encoded in base64 format - */ - BASE64, -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java deleted file mode 100644 index ba5cc2bc27..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.util.Base64; -import java.util.HexFormat; - -/** - * Creates {@link BinaryString} differently encoded strings. - * - * It can create {@link BinaryString} from differently encoded strings and - * returns a sub-type of {@link BinaryString} based on the desired output - * encoding. - * - * @author Jeremias Eppler - */ -public class BinaryStringFactory { - private static final BinaryStringEncodingType DEFAULT_ENCODING_TYPE = BinaryStringEncodingType.BASE64; - - public static BinaryString createFromBytes(byte[] bytes) { - return createFromBytes(bytes, DEFAULT_ENCODING_TYPE); - } - - public static BinaryString createFromBytes(byte[] bytes, BinaryStringEncodingType encodingType) { - if (bytes == null) { - throw new IllegalArgumentException("String cannot be null."); - } - - BinaryString binaryString; - - switch (encodingType) { - case BASE64: - binaryString = new Base64String(bytes); - break; - case HEX: - binaryString = new HexString(bytes); - break; - case PLAIN: - binaryString = new PlainString(bytes); - break; - default: - throw new IllegalArgumentException("Unknown binary string type"); - } - - return binaryString; - } - - /** - * Creates a {@link BinaryString} from a plain Java string. - * - * The BinaryString will be in the default encoding. - * - * @param string - * @return - */ - public static BinaryString createFromString(String string) { - return createFromString(string, DEFAULT_ENCODING_TYPE); - } - - /** - * Creates a {@link BinaryString} from a plain Java string. - * - * It returns the string in the specified encoding. - * - * @param string - * @param encodingType - * @return - */ - public static BinaryString createFromString(String string, BinaryStringEncodingType encodingType) { - if (string == null) { - throw new IllegalArgumentException("String cannot be null."); - } - - BinaryString binaryString; - - switch (encodingType) { - case BASE64: - binaryString = new Base64String(string); - break; - case HEX: - binaryString = new HexString(string); - break; - case PLAIN: - binaryString = new PlainString(string); - break; - default: - throw new IllegalArgumentException("Unknown binary string type"); - } - - return binaryString; - } - - /** - * Create a new {@link BinaryString} from a string which is in hexadecimal - * encoding. - * - * The given string needs to be hexadecimal encoded. - * - * For example: Given the string "616263" (orginal value "abc") it will decode - * it and store it as bytes. - * - * @param stringInHexFormat - * @param encodingType - * @return - */ - public static BinaryString createFromHex(String stringInHexFormat) { - return createFromHex(stringInHexFormat, DEFAULT_ENCODING_TYPE); - } - - /** - * Create a new {@link BinaryString} from a string which is in hexadecimal - * encoding. - * - * The given string needs to be hexadecimal encoded. - * - * For example: Given the string "616263" (orginal value "abc") it will decode - * it and store it as bytes. - * - * @param stringInHexFormat - * @param encodingType - * @return - */ - public static BinaryString createFromHex(String stringInHexFormat, BinaryStringEncodingType encodingType) { - if (stringInHexFormat == null) { - throw new IllegalArgumentException("String cannot be null."); - } - - HexFormat hexFormat = HexFormat.of(); - byte[] hexBytes = hexFormat.parseHex(stringInHexFormat); - - return createFromBytes(hexBytes, encodingType); - } - - /** - * Create a new {@link BinaryString} from a string which is base64 encoded. - * - * The given string needs to be base64 encoded. - * - * For example: Given the string "YWJj" (orginal value "abc") it will decode it - * and store it as bytes. - * - * @param stringInBase64Format - * @return - */ - public static BinaryString createFromBase64(String stringInBase64Format) { - return createFromBase64(stringInBase64Format, DEFAULT_ENCODING_TYPE); - } - - /** - * Create a new {@link BinaryString} from a string which is base64 encoded. - * - * The given string needs to be base64 encoded. - * - * For example: Given the string "YWJj" (orginal value "abc") it will decode it - * and store it as bytes. - * - * @param stringInBase64Format - * @param encodingType - * @return - */ - public static BinaryString createFromBase64(String stringInBase64Format, BinaryStringEncodingType encodingType) { - if (stringInBase64Format == null) { - throw new IllegalArgumentException("String cannot be null."); - } - - // if it cannot be decoded, - // it will throw an IllegalArgumentException - byte[] decoded = Base64.getDecoder().decode(stringInBase64Format); - - return createFromBytes(decoded, encodingType); - } -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java deleted file mode 100644 index 81c8880297..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.util.HexFormat; - -/** - * Hexadecimal encoded string. - * - * @author Jeremias Eppler - */ -public class HexString extends AbstractBinaryString { - - protected HexFormat hexFormat = HexFormat.of(); - - HexString(byte[] bytes) { - super(bytes); - } - - HexString(String string) { - super(string); - } - - @Override - public String toString() { - return hexFormat.formatHex(bytes); - } - - @Override - public BinaryStringEncodingType getType() { - return BinaryStringEncodingType.HEX; - } - -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java deleted file mode 100644 index 65da105f74..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; - -/** - * A cipher which does not encrypt anything. - * - * It can be used during testing or debugging. - * - * Furthermore, this cipher can be used to change the encoding of the data. For - * example, from plain to Base64. - * - * @author Jeremias Eppler - */ -public class NoneCipher implements PersistenceCipher { - private NoneCipher() { - } - - @Override - public PersistenceCipherType getCipherType() { - return PersistenceCipherType.NONE; - } - - public static PersistenceCipher create(BinaryString secret) throws InvalidKeyException { - return new NoneCipher(); - } - - @Override - public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException { - return encrypt(plaintext, initializationVector, BinaryStringEncodingType.PLAIN); - } - - @Override - public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType) - throws InvalidAlgorithmParameterException, InvalidKeyException { - return BinaryStringFactory.createFromString(plaintext, encodingType); - } - - @Override - public String decrypt(BinaryString ciphertext, BinaryString initializationVector) - throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - - return ciphertext.toString(); - } - - @Override - public BinaryString generateNewInitializationVector() { - return generateNewInitializationVector(BinaryStringEncodingType.PLAIN); - } - - @Override - public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType) { - return BinaryStringFactory.createFromString("", encodingType); - } -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java deleted file mode 100644 index bf8ff6049e..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; - -/** - * Interface for cryptographic algorithms used to protect data at rest. - * - * "At rest" refers to data which is usually stored in a database, file or other - * persistent storage. - * - * @author Jeremias Eppler - */ -public interface PersistenceCipher { - public static PersistenceCipher create(BinaryString secret) throws InvalidKeyException { - return null; - } - - public BinaryString generateNewInitializationVector(); - - public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType); - - public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException; - - public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType) - throws InvalidAlgorithmParameterException, InvalidKeyException; - - public String decrypt(BinaryString ciphertext, BinaryString initializationVector) - throws IllegalArgumentException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException; - - public PersistenceCipherType getCipherType(); -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java deleted file mode 100644 index 76b56c83fb..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.security.InvalidKeyException; - -/** - * The factory creates persistent ciphers used to protect data at rest. - * - * The algorithms protect both the confidentiality and integrity of the - * information at rest. - * - * In addition, the ciphers which it creates are nonce (initialization vector) - * misuse-resistant. - * - * @author Jeremias Eppler - */ -public class PersistenceCipherFactory { - - /** - * Creates a new persistent cipher based on the type and secret. - * - * @param cipherType - * @param secret - * @return - * @throws InvalidKeyException - */ - public static PersistenceCipher create(PersistenceCipherType cipherType, BinaryString secret) throws InvalidKeyException { - PersistenceCipher cipher = null; - - switch (cipherType) { - case NONE: - cipher = NoneCipher.create(secret); - break; - case AES_GCM_SIV_256: - case AES_GCM_SIV_128: - cipher = AesGcmSiv.create(secret); - break; - default: - throw new IllegalArgumentException("Unable to create cipher. Unknown cipher."); - } - - return cipher; - } -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java deleted file mode 100644 index 8442acca71..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -/** - * Contains the available ciphers. - * - * @author Jeremias Eppler - */ -public enum PersistenceCipherType { - /** - * A special cipher type which does not protect data. - * - * This is intended for testing. - */ - NONE, - - /** - * Advanced Encryption Standard (AES) 128 bit key (secret) in Galois/Counter - * Mode (GCM) and synthetic initialization vector (SIV). - * - * AES GCM-SIV is a nonce (initialization vector) misuse-resistant authenticated - * encryption. - * - * @see https://datatracker.ietf.org/doc/html/rfc8452 - */ - AES_GCM_SIV_128, - - /** - * Advanced Encryption Standard (AES) 256 bit key (secret) in Galois/Counter - * Mode (GCM) and synthetic initialization vector (SIV). - * - * AES GCM-SIV is a nonce (initialization vector) misuse-resistant authenticated - * encryption. - * - * @see https://datatracker.ietf.org/doc/html/rfc8452 - */ - AES_GCM_SIV_256; -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java deleted file mode 100644 index 8af0fac45f..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -/** - * A wrapper around the string class. - * - * The string class in Java is final and therefore it is impossible to inherited - * from. - * - * @author Jeremias Eppler - */ -public class PlainString extends AbstractBinaryString { - - PlainString(byte[] bytes) { - super(bytes); - } - - PlainString(String string) { - super(string); - } - - @Override - public byte[] getBytes() { - return bytes; - } - - @Override - public BinaryStringEncodingType getType() { - return BinaryStringEncodingType.PLAIN; - } - - @Override - public String toString() { - return new String(bytes); - } - -} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java deleted file mode 100644 index a118d9b87c..0000000000 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; - -/** - * The rotation strategy helps to rotate the cipher text. - * - * It can be used to rotate (re-encrpyt) by using: - different initialization - * vectors - different secret keys - different algorithms - * - * or a combination of the above. - * - * @author Jeremias Eppler - */ -public class RotationStrategy { - private PersistenceCipher currentCipher; - private PersistenceCipher newCipher; - private boolean performSecretRotation = false; - - private RotationStrategy(PersistenceCipher currentCipher, PersistenceCipher newCipher, boolean performSecretRotation) { - this.currentCipher = currentCipher; - this.newCipher = newCipher; - this.performSecretRotation = performSecretRotation; - } - - /** - * Create a new rotation strategy which only allows to rotate the initialization - * vector. - * - * @param secret - * @param cipher - * @return - * @throws InvalidKeyException - */ - public static RotationStrategy createInitializationVectorOnlyRotationStrategy(BinaryString secret, PersistenceCipherType cipher) - throws InvalidKeyException { - PersistenceCipher currentCipher = PersistenceCipherFactory.create(cipher, secret); - PersistenceCipher newCipher = PersistenceCipherFactory.create(cipher, secret); - - boolean performSecretRotation = false; - - return new RotationStrategy(currentCipher, newCipher, performSecretRotation); - } - - /** - * Create a new rotation strategy which allows to rotate the secret. - * - * This is useful in case of a secret leak. - * - * @param currentSecret - * @param newSecret - * @param cipher - * @return - * @throws InvalidKeyException - */ - public static RotationStrategy createSecretRotationStrategy(BinaryString currentSecret, BinaryString newSecret, PersistenceCipherType cipher) - throws InvalidKeyException { - PersistenceCipher currentCipher = PersistenceCipherFactory.create(cipher, currentSecret); - PersistenceCipher newCipher = PersistenceCipherFactory.create(cipher, newSecret); - - boolean performSecretRotation = true; - - return new RotationStrategy(currentCipher, newCipher, performSecretRotation); - } - - /** - * Create a new rotation strategy which allows to rotate the secret and ciphers. - * - * This is useful if the underling cryptographic cipher or mode of operation is - * deemed as insecure. - * - * For example, this is the case with Data Encryption Standard (DES) and the - * Triple DES variant. - * - * - * @param currentSecret - * @param newSecret - * @param currentCipherType - * @param newCipherType - * @return - * @throws InvalidKeyException - */ - public static RotationStrategy createCipherAndSecretRotationStrategy(BinaryString currentSecret, BinaryString newSecret, - PersistenceCipherType currentCipherType, PersistenceCipherType newCipherType) throws InvalidKeyException { - PersistenceCipher currentCipher = PersistenceCipherFactory.create(currentCipherType, currentSecret); - PersistenceCipher newCipher = PersistenceCipherFactory.create(newCipherType, newSecret); - - boolean performSecretRotation = true; - - return new RotationStrategy(currentCipher, newCipher, performSecretRotation); - } - - /** - * Rotate the encrypted cipher text using the same initialization vector. - * - * The given cipher text is decrypted and encrypted again using the given - * initialization vector the old and the new cipher text. - * - * @param cipherText - * @param initializationVector - * @return - * @throws InvalidKeyException - * @throws IllegalArgumentException - * @throws InvalidAlgorithmParameterException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - */ - public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector) - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - return rotate(cipherText, initializationVector, null, cipherText.getType()); - } - - /** - * Rotate the encrypted cipher text using different initialization vectors. - * - * The given cipher text is decrypted and encrypted again using two different - * initialization vectors. - * - * @param cipherText - * @param initializationVector - * @param newIntializationVector - * @return - * @throws InvalidKeyException - * @throws IllegalArgumentException - * @throws InvalidAlgorithmParameterException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - */ - public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector, BinaryString newIntializationVector) - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - return rotate(cipherText, initializationVector, newIntializationVector, cipherText.getType()); - } - - /** - * Rotate the encrypted cipher text using different initialization vectors and - * the given encoding type. - * - * The given cipher text is decrypted and encrypted again using two different - * initialization vectors. - * - * The resulting cipher text is returned in the specified - * {@link BinaryStringEncodingType}. - * - * @param cipherText - * @param initializationVector - * @param newIntializationVector - * @param newBinaryStringEncoding - * @return - * @throws InvalidKeyException - * @throws IllegalArgumentException - * @throws InvalidAlgorithmParameterException - * @throws IllegalBlockSizeException - * @throws BadPaddingException - */ - public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector, BinaryString newIntializationVector, - BinaryStringEncodingType newBinaryStringEncoding) - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - if (cipherText == null) { - throw new IllegalArgumentException("The ciphertext cannot be null!"); - } - - if (initializationVector == null) { - throw new IllegalArgumentException("The initialization vector (nonce) cannot be null!"); - } - - String plainText = currentCipher.decrypt(cipherText, initializationVector); - - BinaryString newCipherText = null; - - if (newIntializationVector != null) { - newCipherText = newCipher.encrypt(plainText, newIntializationVector); - } else { - newCipherText = newCipher.encrypt(plainText, initializationVector); - } - - return newCipherText; - } - - public PersistenceCipherType getCurrentCipher() { - return currentCipher.getCipherType(); - } - - public PersistenceCipherType getNewCipher() { - return newCipher.getCipherType(); - } - - public boolean isSecretRotationStrategy() { - return performSecretRotation; - } - - public boolean isCipherRotationStrategy() { - return currentCipher.getCipherType() != newCipher.getCipherType(); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java deleted file mode 100644 index d011f15ae7..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java +++ /dev/null @@ -1,377 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.*; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.AEADBadTagException; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; - -import org.junit.jupiter.api.Test; - -public class AesGcmSivTest { - private static final String LONG_TEXT = "Hello world, this is long text with different emojis. Today, I had for breakfast two 🥐, 1 🥑 and some 🥪. That made me happy ☺️!"; - - @Test - void generate_new_initialization_vector() throws InvalidKeyException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(32)); - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - BinaryString initializationVector = cipher.generateNewInitializationVector(); - - /* test */ - assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length); - } - - @Test - void create_secret_32_bytes() throws InvalidKeyException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(32)); - - /* execute */ - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* test */ - assertNotNull(cipher); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); - } - - @Test - void create_secret_16_bytes() throws InvalidKeyException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(16)); - - /* execute */ - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* test */ - assertNotNull(cipher); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); - } - - @Test - void create_secret_secret_6_bytes_invalid() { - /* prepare */ - BinaryString secret = new Base64String("abcdef"); - - /* execute + test */ - assertThrows(InvalidKeyException.class, () -> { - AesGcmSiv.create(secret); - }); - } - - @Test - void create_secret_secret_31_bytes_invalid() { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(31)); - - /* execute + test */ - assertThrows(InvalidKeyException.class, () -> { - AesGcmSiv.create(secret); - }); - } - - @Test - void encrypt__aes_256() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, - IllegalBlockSizeException, BadPaddingException { - - /* prepare */ - BinaryString secret = new Base64String("w".repeat(32)); - String plaintext = "bca"; - String expectedCiphertext = "1qKKtEpM2ppl4wWrJxJo0MiFdw=="; - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); - - /* test */ - assertEquals(expectedCiphertext, ciphertext.toString()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); - } - - @Test - void encrypt__aes_256_initialization_vector_too_short() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, - InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - - /* prepare */ - BinaryString secret = new Base64String("w".repeat(32)); - String plaintext = "bca"; - BinaryString initializationVector = new Base64String("abc".repeat(2)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - Exception exception = assertThrows(InvalidAlgorithmParameterException.class, () -> { - cipher.encrypt(plaintext, initializationVector); - }); - - /* test */ - assertEquals("Invalid nonce", exception.getMessage()); - } - - @Test - void encrypt__aes_256_initialization_vector_too_long() throws InvalidKeyException, InvalidAlgorithmParameterException { - - /* prepare */ - BinaryString secret = new Base64String("w".repeat(32)); - String plaintext = "bca"; - BinaryString initializationVector = new Base64String("abc".repeat(50)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - Exception exception = assertThrows(InvalidAlgorithmParameterException.class, () -> { - cipher.encrypt(plaintext, initializationVector); - }); - - /* test */ - assertEquals("Invalid nonce", exception.getMessage()); - } - - @Test - void decrypt__aes_256() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, - IllegalBlockSizeException, BadPaddingException { - /* prepare */ - BinaryString secret = new Base64String("w".repeat(32)); - String expectedPlaintext = "bca"; - BinaryString ciphertext = BinaryStringFactory.createFromBase64("1qKKtEpM2ppl4wWrJxJo0MiFdw==", BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - String plaintext = cipher.decrypt(ciphertext, initializationVector); - - /* test */ - assertEquals(expectedPlaintext, plaintext); - } - - @Test - void encrypt__aes_128() throws InvalidKeyException, InvalidAlgorithmParameterException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(16)); - String plaintext = "bca"; - String expectedCiphertext = "yGcKhuWbewS+R4tlegECshiTSQ=="; - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); - - /* test */ - assertEquals(expectedCiphertext, ciphertext.toString()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); - } - - @Test - void encrypt__aes_256_hex_format_and_emojis() throws InvalidKeyException, InvalidAlgorithmParameterException { - /* prepare */ - BinaryString secret = new HexString("🥦🥕🥔🫘🥒🫑🌽🍆"); - String plaintext = "Hello 👋, welcome to 🌐."; - String expectedCiphertext = "d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6"; - BinaryString initializationVector = new Base64String("🧅".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector, BinaryStringEncodingType.HEX); - - /* test */ - assertEquals(expectedCiphertext, ciphertext.toString()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); - } - - @Test - void encrypt__aes_128_base64_format_and_emojis() throws InvalidKeyException, InvalidAlgorithmParameterException { - /* prepare */ - BinaryString secret = new Base64String("🍐🍌🍓🍉"); - - String plaintext = "Hello 👋, welcome to 🌐."; - String expectedCiphertext = "Qu7ICJBGMw9dAPPBWx86e5bjOq3YKC+x25n/YkluWZAGdSna08tKaE78pMk="; - BinaryString initializationVector = new Base64String("🧅".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector, BinaryStringEncodingType.BASE64); - - /* test */ - assertEquals(expectedCiphertext, ciphertext.toString()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); - } - - @Test - void decrypt__aes_128() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, - IllegalBlockSizeException, BadPaddingException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(16)); - String expectedPlaintext = "bca"; - BinaryString cipherText = BinaryStringFactory.createFromBase64("yGcKhuWbewS+R4tlegECshiTSQ==", BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - String plaintext = cipher.decrypt(cipherText, initializationVector); - - /* test */ - assertEquals(expectedPlaintext, plaintext); - } - - @Test - void decrypt__aes_128_wrong_cipher_text() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, - IllegalBlockSizeException, BadPaddingException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(16)); - BinaryString ciphertext = new Base64String("hello world, this is base 64 encoded"); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - Exception exception = assertThrows(AEADBadTagException.class, () -> { - cipher.decrypt(ciphertext, initializationVector); - }); - - /* test */ - assertEquals("mac check failed", exception.getMessage()); - } - - @Test - void decrypt__aes_256_wrong_cipher_text() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, - IllegalBlockSizeException, BadPaddingException { - - /* prepare */ - BinaryString secret = new Base64String("a".repeat(32)); - BinaryString ciphertext = new Base64String("hello world, this is base 64 encoded"); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - Exception exception = assertThrows(AEADBadTagException.class, () -> { - cipher.decrypt(ciphertext, initializationVector); - }); - - /* test */ - assertEquals("mac check failed", exception.getMessage()); - } - - @Test - void encrypt__aes_128_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, - InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - - /* prepare */ - BinaryString secret = new Base64String("a".repeat(16)); - String plaintext = LONG_TEXT; - BinaryString expectedCiphertext = BinaryStringFactory.createFromBase64( - "28/RdEWgYbpbiraiWcSo58+8sfCRRQpSoZiiFqNsYN8tLLVE6AXeQjxh4zazK65G7T0dmFnqrbyx6aRUB+7I6guFXMxqjRij9HdRkae4OalWZVNtCs2+mjBBMNOB5Ke2bgIcYDZbDMRWceBtJnE5PKg7vxrNFgR+8uFw9ejbRVxzGTbkyNeh48QVT9Knk7LpmqQ/eHFTvsvnD0M=", - BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); - - /* test */ - assertEquals(expectedCiphertext, ciphertext); - } - - @Test - void encrypt__aes_256_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, - InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - - /* prepare */ - BinaryString secret = new Base64String("a".repeat(32)); - String plaintext = LONG_TEXT; - BinaryString expectedCiphertext = BinaryStringFactory.createFromBase64( - "E2RrqhXtKG39okWxxvw3d4NQ+DXr2+Qa78JvdpHS4+FOckRECTkjoX2JfZNKHP3on0sDO1q8uTc+BY9QJkMK+MsWzp8YT4SR0UxWo7uy5SSPMXOLLcQg0vzTOTdgo00vPQy34vogNYO1V/TTzOzzP6Ng0kT9TDsYUWu+v0y3uZw/ujl2X8bP8Nfrp2ZRMrgfpj7NbjQQd8hD5AY=", - BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); - - /* test */ - assertEquals(expectedCiphertext, ciphertext); - } - - @Test - void decrypt__aes_128_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, - InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - - /* prepare */ - BinaryString secret = new Base64String("a".repeat(16)); - String expectedPlaintext = LONG_TEXT; - BinaryString ciphertext = BinaryStringFactory.createFromBase64( - "28/RdEWgYbpbiraiWcSo58+8sfCRRQpSoZiiFqNsYN8tLLVE6AXeQjxh4zazK65G7T0dmFnqrbyx6aRUB+7I6guFXMxqjRij9HdRkae4OalWZVNtCs2+mjBBMNOB5Ke2bgIcYDZbDMRWceBtJnE5PKg7vxrNFgR+8uFw9ejbRVxzGTbkyNeh48QVT9Knk7LpmqQ/eHFTvsvnD0M=", - BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - String plaintext = cipher.decrypt(ciphertext, initializationVector); - - /* test */ - assertEquals(expectedPlaintext, plaintext); - } - - @Test - void decrypt__aes_256_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, - InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - - /* prepare */ - BinaryString secret = new Base64String("a".repeat(32)); - String expectedPlaintext = LONG_TEXT; - BinaryString ciphertext = BinaryStringFactory.createFromBase64( - "E2RrqhXtKG39okWxxvw3d4NQ+DXr2+Qa78JvdpHS4+FOckRECTkjoX2JfZNKHP3on0sDO1q8uTc+BY9QJkMK+MsWzp8YT4SR0UxWo7uy5SSPMXOLLcQg0vzTOTdgo00vPQy34vogNYO1V/TTzOzzP6Ng0kT9TDsYUWu+v0y3uZw/ujl2X8bP8Nfrp2ZRMrgfpj7NbjQQd8hD5AY=", - BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* execute */ - String plaintext = cipher.decrypt(ciphertext, initializationVector); - - /* test */ - assertEquals(expectedPlaintext, plaintext); - } - - @Test - void getCipherType_aes_256() throws InvalidKeyException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(32)); - - /* execute */ - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* test */ - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType()); - } - - @Test - void getCiphersType_aes_128() throws InvalidKeyException { - /* prepare */ - BinaryString secret = new Base64String("a".repeat(16)); - - /* execute */ - AesGcmSiv cipher = AesGcmSiv.create(secret); - - /* test */ - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType()); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java deleted file mode 100644 index 40e6fb3fd1..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class Base64StringTest { - @Test - void from_string() { - /* prepare */ - String string = "Hello"; - String expectedString = "SGVsbG8="; - - /* execute + test */ - assertEquals(expectedString, new Base64String(string).toString()); - } - - @Test - void from_string_null_throw_illegal_argument_exception() { - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - new Base64String((String) null); - }); - - /* test */ - assertEquals("String cannot be null.", exception.getMessage()); - } - - @Test - void from_string_unicode() { - String string = "Hello 🦄"; - String expectedString = "SGVsbG8g8J+mhA=="; - - assertEquals(expectedString, new Base64String(string).toString()); - } - - @Test - void from_bytes() { - byte[] bytes = "Hello".getBytes(); - String expectedString = "SGVsbG8="; - - assertEquals(expectedString, new Base64String(bytes).toString()); - } - - @Test - void from_bytes_unicode() { - byte[] bytes = "Hello 🦄".getBytes(); - String expectedString = "SGVsbG8g8J+mhA=="; - - assertEquals(expectedString, new Base64String(bytes).toString()); - } - - @Test - void from_bytes_null_throw_illegal_argument_exception() { - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - new Base64String((byte[]) null); - }); - - assertEquals("Byte array cannot be null.", exception.getMessage()); - } - - @Test - void toString_unicode_test() { - String string = "Hello 🦄"; - String expectedString = "SGVsbG8g8J+mhA=="; - - assertEquals(expectedString, new Base64String(string).toString()); - } - - @Test - void equals_test() { - String string = "I like 🍍"; - - Base64String b64String = new Base64String(string); - Base64String b64String2 = new Base64String(string); - - assertEquals(b64String, b64String2); - assertTrue(b64String.equals(b64String2)); - assertEquals(b64String.hashCode(), b64String2.hashCode()); - } - - @Test - void test_immutablitiy() { - String string = "A 🐺 in a 🐑 skin"; - Base64String b64String = new Base64String(string); - - assertFalse(string == b64String.toString()); - assertFalse(string.getBytes() == b64String.getBytes()); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java deleted file mode 100644 index 856f9d6bd3..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java +++ /dev/null @@ -1,271 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class BinaryStringFactoryTest { - @Test - void create_from_string_no_type_given() { - /* prepare */ - String string = "hello"; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromString(string); - - /* test */ - assertNotNull(binaryString); - assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64); - assertTrue(binaryString instanceof Base64String); - assertEquals("aGVsbG8=", binaryString.toString()); - } - - @Test - void create_from_string_base64_type_given() { - /* prepare */ - String string = "Hello 🌌!"; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.BASE64); - - /* test */ - assertNotNull(binaryString); - assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64); - assertTrue(binaryString instanceof Base64String); - assertEquals("SGVsbG8g8J+MjCE=", binaryString.toString()); - } - - @Test - void create_from_base64_no_type_given() { - /* prepare */ - String string = "SGVsbG8g8J+MjCE="; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromBase64(string); - - /* test */ - assertNotNull(binaryString); - assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64); - assertTrue(binaryString instanceof Base64String); - assertEquals("SGVsbG8g8J+MjCE=", binaryString.toString()); - } - - @Test - void createFromBase64_null_input() { - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - BinaryStringFactory.createFromBase64(null); - }); - - /* test */ - assertEquals("String cannot be null.", exception.getMessage()); - } - - @Test - void createFromHex_null_input() { - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - BinaryStringFactory.createFromHex(null); - }); - - /* test */ - assertEquals("String cannot be null.", exception.getMessage()); - } - - @Test - void create_from_string_plain_type_given() { - /* prepare */ - String string = "hello"; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.PLAIN); - - /* test */ - assertNotNull(binaryString); - assertEquals(binaryString.getType(), BinaryStringEncodingType.PLAIN); - assertTrue(binaryString instanceof PlainString); - assertEquals("hello", binaryString.toString()); - } - - @Test - void create_from_string_hex_type_given() { - /* prepare */ - String string = "hello"; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.HEX); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.HEX, binaryString.getType()); - assertTrue(binaryString instanceof HexString); - assertEquals("68656c6c6f", binaryString.toString()); - } - - @Test - void create_from_string_no_type_given_and_input_null() { - /* prepare */ - String string = null; - - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - BinaryStringFactory.createFromString(string); - }); - - /* test */ - assertEquals("String cannot be null.", exception.getMessage()); - } - - @Test - void create_from_bytes_no_type_given() { - /* prepare */ - byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); - assertTrue(binaryString instanceof Base64String); - assertEquals("aGVsbG8=", binaryString.toString()); - } - - @Test - void create_from_bytes_base64_type_given() { - /* prepare */ - byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.BASE64); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); - assertTrue(binaryString instanceof Base64String); - assertEquals("aGVsbG8=", binaryString.toString()); - } - - @Test - void create_from_bytes_plain_type_given() { - /* prepare */ - byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.PLAIN); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType()); - assertTrue(binaryString instanceof PlainString); - assertEquals("hello", binaryString.toString()); - } - - @Test - void create_from_bytes_hex_type_given() { - /* prepare */ - byte[] bytes = new byte[] { 104, 101, 108, 108, 111 }; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.HEX); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.HEX, binaryString.getType()); - assertTrue(binaryString instanceof HexString); - assertEquals("68656c6c6f", binaryString.toString()); - } - - @Test - void create_from_bytes_no_type_given_and_input_null() { - /* prepare */ - byte[] bytes = null; - - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - BinaryStringFactory.createFromBytes(bytes); - }); - - /* test */ - assertEquals("String cannot be null.", exception.getMessage()); - } - - @Test - void createFromHex_from_string_plain_type_given() { - /* prepare */ - String string = "68656c6c6f"; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromHex(string, BinaryStringEncodingType.PLAIN); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType()); - assertTrue(binaryString instanceof PlainString); - assertEquals("hello", binaryString.toString()); - } - - @Test - void createFromHex_base64_type_given() { - /* prepare */ - String string = "68656c6c6f"; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromHex(string, BinaryStringEncodingType.BASE64); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); - assertTrue(binaryString instanceof Base64String); - assertEquals("aGVsbG8=", binaryString.toString()); - } - - @Test - void createFromHex_no_type_given() { - /* prepare */ - String string = "68656c6c6f"; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromHex(string); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType()); - assertTrue(binaryString instanceof Base64String); - assertEquals("aGVsbG8=", binaryString.toString()); - } - - @Test - void createFromBase64_plain_type_given() { - /* prepare */ - String string = "aGVsbG8="; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromBase64(string, BinaryStringEncodingType.PLAIN); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType()); - assertTrue(binaryString instanceof PlainString); - assertEquals("hello", binaryString.toString()); - } - - @Test - void createFromBase64_hex_type_given() { - /* prepare */ - String string = "aGVsbG8="; - - /* execute */ - BinaryString binaryString = BinaryStringFactory.createFromBase64(string, BinaryStringEncodingType.HEX); - - /* test */ - assertNotNull(binaryString); - assertEquals(BinaryStringEncodingType.HEX, binaryString.getType()); - assertTrue(binaryString instanceof HexString); - assertEquals("68656c6c6f", binaryString.toString()); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java deleted file mode 100644 index 71e15831e4..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class HexStringTest { - @Test - void from_string() { - /* prepare */ - String string = "Hello"; - String expectedString = "48656c6c6f"; - - /* execute + test */ - assertEquals(expectedString, new HexString(string).toString()); - } - - @Test - void from_string_null_throw_illegal_argument_exception() { - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - new HexString((String) null); - }); - - /* test */ - assertEquals("String cannot be null.", exception.getMessage()); - } - - @Test - void from_string_unicode() { - /* prepare */ - String string = "Hello 🦄"; - String expectedString = "48656c6c6f20f09fa684"; - - /* execute + test */ - assertEquals(expectedString, new HexString(string).toString()); - } - - @Test - void from_bytes() { - /* prepare */ - byte[] bytes = "Hello".getBytes(); - String expectedString = "48656c6c6f"; - - /* execute + test */ - assertEquals(expectedString, new HexString(bytes).toString()); - } - - @Test - void from_bytes_unicode() { - /* prepare */ - byte[] bytes = "Hello 🦄".getBytes(); - String expectedString = "48656c6c6f20f09fa684"; - - /* execute + test */ - assertEquals(expectedString, new HexString(bytes).toString()); - } - - @Test - void from_bytes_null_throw_illegal_argument_exception() { - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - new HexString((byte[]) null); - }); - - /* test */ - assertEquals("Byte array cannot be null.", exception.getMessage()); - } - - @Test - void toString_unicode_test() { - /* prepare */ - String string = "Hello 🦄"; - String expectedString = "48656c6c6f20f09fa684"; - - /* execute + test */ - assertEquals(expectedString, new HexString(string).toString()); - } - - @Test - void equals_test() { - /* prepare */ - String string = "I like 🍍"; - - /* execute */ - HexString hexString = new HexString(string); - HexString hexString2 = new HexString(string); - - /* test */ - assertEquals(hexString, hexString2); - assertTrue(hexString.equals(hexString2)); - assertEquals(hexString.hashCode(), hexString2.hashCode()); - } - - @Test - void test_immutablitiy() { - /* prepare */ - String string = "A 🐺 in a 🐑 skin"; - - /* execute */ - HexString hexString = new HexString(string); - - /* test */ - assertFalse(string == hexString.toString()); - assertFalse(string.getBytes() == hexString.getBytes()); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java deleted file mode 100644 index 1369365bf5..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; - -import org.junit.jupiter.api.Test; - -public class NoneCipherTest { - private BinaryString initializationVector = BinaryStringFactory.createFromString("Hello"); - - @Test - void encrypt_null_iv() throws InvalidKeyException, InvalidAlgorithmParameterException { - /* prepare */ - String plaintext = "This is plaintext"; - PersistenceCipher cipher = NoneCipher.create(null); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, null); - - /* test */ - assertEquals(plaintext, ciphertext.toString()); - } - - @Test - void encrypt_with_iv() throws InvalidKeyException, InvalidAlgorithmParameterException { - /* prepare */ - String plaintext = "This is plaintext"; - PersistenceCipher cipher = NoneCipher.create(null); - - /* execute */ - BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector); - - /* test */ - assertEquals(plaintext, ciphertext.toString()); - } - - @Test - void decrypt_null_iv() throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlaintext = "This is plaintext"; - BinaryString ciphertext = new PlainString(expectedPlaintext); - PersistenceCipher cipher = NoneCipher.create(null); - - /* execute */ - String plaintext = cipher.decrypt(ciphertext, null); - - /* test */ - assertEquals(expectedPlaintext, plaintext); - } - - @Test - void decrypt_with_iv() throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlaintext = "This is plaintext with a 🐎!"; - BinaryString ciphertext = new PlainString(expectedPlaintext); - PersistenceCipher cipher = NoneCipher.create(null); - - /* execute */ - String plaintext = cipher.decrypt(ciphertext, initializationVector); - - /* test */ - assertEquals(expectedPlaintext, plaintext); - } - - @Test - void getCipher_null() throws InvalidKeyException { - /* prepare */ - PersistenceCipher cipher = NoneCipher.create(null); - - /* execute + test */ - assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); - } - - @Test - void getCipher_string() throws InvalidKeyException { - /* prepare */ - PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello")); - - /* execute + test */ - assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); - } - - @Test - void generateNewInitializationVector_default_type() throws InvalidKeyException { - /* prepare */ - PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello")); - - /* execute */ - BinaryString initializationVector = cipher.generateNewInitializationVector(); - - /* test */ - assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); - assertEquals(BinaryStringEncodingType.PLAIN, initializationVector.getType()); - assertEquals("", initializationVector.toString()); - } - - @Test - void generateNewInitializationVector_type_provided() throws InvalidKeyException { - /* prepare */ - PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello")); - - /* execute */ - BinaryString initializationVector = cipher.generateNewInitializationVector(BinaryStringEncodingType.HEX); - - /* test */ - assertEquals(PersistenceCipherType.NONE, cipher.getCipherType()); - assertEquals(BinaryStringEncodingType.HEX, initializationVector.getType()); - assertEquals("", initializationVector.toString()); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java deleted file mode 100644 index 96e5978829..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.security.InvalidKeyException; - -import org.junit.jupiter.api.Test; - -public class PersistenceCipherFactoryTest { - @Test - void create_none_cipher() throws InvalidKeyException { - /* prepare */ - PersistenceCipherType cipherType = PersistenceCipherType.NONE; - Base64String secret = new Base64String("topSecret"); - - /* execute */ - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - - /* test */ - assertEquals(cipher.getCipherType(), cipherType); - } - - @Test - void create_aes_gcm_siv_256() throws InvalidKeyException { - /* prepare */ - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; - BinaryString secret = new PlainString("a".repeat(32)); - - /* execute */ - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - - /* test */ - assertEquals(cipher.getCipherType(), cipherType); - } - - @Test - void create_aes_gcm_siv_128() throws InvalidKeyException { - /* prepare */ - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; - BinaryString secret = new HexString("a".repeat(16)); - - /* execute */ - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - - /* test */ - assertEquals(cipher.getCipherType(), cipherType); - } - - @Test - void create_initializationVector_aes_gcm_siv_256() throws InvalidKeyException { - /* prepare */ - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; - BinaryString secret = new PlainString("a".repeat(32)); - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - - /* execute */ - BinaryString initializationVector = cipher.generateNewInitializationVector(); - - /* test */ - assertEquals(cipher.getCipherType(), cipherType); - assertNotNull(initializationVector); - assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length); - } - - @Test - void create_initializationVector_aes_gcm_siv_128() throws InvalidKeyException { - /* prepare */ - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; - BinaryString secret = new PlainString("a".repeat(16)); - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - - /* execute */ - BinaryString initializationVector = cipher.generateNewInitializationVector(); - - /* test */ - assertEquals(cipher.getCipherType(), cipherType); - assertNotNull(initializationVector); - assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length); - } - - @Test - void create_initializationVector_none_cipher() throws InvalidKeyException { - /* prepare */ - PersistenceCipherType cipherType = PersistenceCipherType.NONE; - BinaryString secret = new PlainString("a".repeat(3)); - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - - /* execute */ - BinaryString initializationVector = cipher.generateNewInitializationVector(); - - /* test */ - assertEquals(cipher.getCipherType(), cipherType); - assertNotNull(initializationVector); - assertEquals("", initializationVector.toString()); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java deleted file mode 100644 index b9b66d26f1..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class PlainStringTest { - @Test - void from_string() { - /* prepare */ - String string = "Hello"; - - /* execute + test */ - assertEquals(string, new PlainString(string).toString()); - } - - @Test - void from_string_null_throw_illegal_argument_exception() { - /* execute */ - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - new PlainString((String) null); - }); - - /* test */ - assertEquals("String cannot be null.", exception.getMessage()); - } - - @Test - void from_string_unicode() { - /* prepare */ - String string = "Hello 🦄"; - - /* execute + test */ - assertEquals(string, new PlainString(string).toString()); - } - - @Test - void from_bytes() { - /* prepare */ - byte[] bytes = "Hello".getBytes(); - - /* execute + test */ - assertEquals(new String(bytes), new PlainString(bytes).toString()); - } - - @Test - void from_bytes_unicode() { - /* prepare */ - byte[] bytes = "Hello 🦄".getBytes(); - - /* execute + test */ - assertEquals(new String(bytes), new PlainString(bytes).toString()); - } - - @Test - void from_bytes_null_throw_illegal_argument_exception() { - Exception exception = assertThrows(IllegalArgumentException.class, () -> { - new PlainString((byte[]) null); - }); - - /* execute + test */ - assertEquals("Byte array cannot be null.", exception.getMessage()); - } - - @Test - void toString_unicode_test() { - /* prepare */ - String string = "Hello 🦄"; - - /* execute + test */ - assertEquals(string, new PlainString(string).toString()); - } - - @Test - void equals_test() { - /* prepare */ - String string = "I like 🍍"; - - /* execute */ - PlainString plainString1 = new PlainString(string); - PlainString plainString2 = new PlainString(string); - - /* test */ - assertEquals(plainString1, plainString2); - assertTrue(plainString1.equals(plainString2)); - assertEquals(plainString1.hashCode(), plainString2.hashCode()); - } - - @Test - void test_immutablitiy() { - /* prepare */ - String string = "A 🐺 in a 🐑 skin"; - - /* execute */ - PlainString plainString = new PlainString(string); - - /* test */ - assertFalse(string == plainString.toString()); - assertFalse(string.getBytes() == plainString.getBytes()); - } -} diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java deleted file mode 100644 index 3b656487d8..0000000000 --- a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java +++ /dev/null @@ -1,300 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.core.security.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; - -import org.junit.jupiter.api.Test; - -public class RotationTest { - @Test - void secret_rotation_cipher_none() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - BinaryString currentSecret = new PlainString("abc"); - BinaryString newSecret = new PlainString("bca"); - BinaryString cipherText = new PlainString("hello"); - BinaryString initializationVector = new PlainString("iv"); - - PersistenceCipherType cipherType = PersistenceCipherType.NONE; - RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(cipherText, newCipherText); - assertTrue(rotation.isSecretRotationStrategy()); - assertFalse(rotation.isCipherRotationStrategy()); - assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher()); - } - - @Test - void secret_rotation_cipher_aes_gcm_siv_128() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlainText = "Hello, I am ☺️ 4 you."; - BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("8gWa4YPRlshBZCml8a0xvAJ1Y1mNU9iovclpvOhwVj4XiiaZvWKHWkU=", - BinaryStringEncodingType.BASE64); - BinaryString currentSecret = new PlainString("a".repeat(16)); - BinaryString newSecret = new PlainString("z".repeat(16)); - BinaryString cipherText = BinaryStringFactory.createFromBase64("DuwfqoAJrzZiK3u5v0XEnARPOjLugpobvWCxfTV6Y1FkAOECII/J8RU=", - BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new PlainString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; - RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(expectedCipherText, newCipherText); - assertTrue(rotation.isSecretRotationStrategy()); - assertFalse(rotation.isCipherRotationStrategy()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher()); - - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, newSecret); - String plainText = cipher.decrypt(newCipherText, initializationVector); - - assertEquals(expectedPlainText, plainText); - } - - @Test - void secret_rotation_cipher_aes_gcm_siv_256() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlainText = "Hello, I am ☺️ 4 you."; - BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("y5/I7wBmKwqKKazHrRnj2j6v9uuySiHJ9MK3pUvWjkUIC/3jDSFJLPI=", - BinaryStringEncodingType.BASE64); - BinaryString currentSecret = new PlainString("a".repeat(32)); - BinaryString newSecret = new PlainString("z".repeat(32)); - BinaryString cipherText = BinaryStringFactory.createFromBase64("KSIAj+JAD95o77GF91GQShbuh0dyuIMdDjhX1VQFi7DW2KSutD0uOt8=", - BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new HexString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; - RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(expectedCipherText, newCipherText); - assertTrue(rotation.isSecretRotationStrategy()); - assertFalse(rotation.isCipherRotationStrategy()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher()); - - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, newSecret); - String plainText = cipher.decrypt(newCipherText, initializationVector); - - assertEquals(expectedPlainText, plainText); - } - - @Test - void cipher_and_secret_rotation_cipher_none_to_cipher_aes_gcm_siv_128() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlainText = "Hello, I am ☺️ 4 you."; - BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("DuwfqoAJrzZiK3u5v0XEnARPOjLugpobvWCxfTV6Y1FkAOECII/J8RU=", - BinaryStringEncodingType.BASE64); - BinaryString currentSecret = new PlainString("abc"); - BinaryString newSecret = new PlainString("a".repeat(16)); - BinaryString cipherText = new PlainString(expectedPlainText); - BinaryString initializationVector = new PlainString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES)); - - PersistenceCipherType currentCipherType = PersistenceCipherType.NONE; - PersistenceCipherType newCipherType = PersistenceCipherType.AES_GCM_SIV_128; - RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(expectedCipherText, newCipherText); - assertTrue(rotation.isSecretRotationStrategy()); - assertTrue(rotation.isCipherRotationStrategy()); - assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher()); - - PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret); - String plainText = cipher.decrypt(newCipherText, initializationVector); - - assertEquals(expectedPlainText, plainText); - } - - @Test - void cipher_and_secret_rotation_cipher_aes_gcm_siv_128_to_cipher_aes_gcm_siv_256() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlainText = "Hello 👋, welcome to 🌐."; - - BinaryString expectedCipherText = BinaryStringFactory - .createFromHex("d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6", BinaryStringEncodingType.BASE64); - BinaryString currentSecret = new Base64String("🍐🍌🍓🍉"); - BinaryString newSecret = new HexString("🥦🥕🥔🫘🥒🫑🌽🍆"); - BinaryString cipherText = BinaryStringFactory.createFromBase64("Qu7ICJBGMw9dAPPBWx86e5bjOq3YKC+x25n/YkluWZAGdSna08tKaE78pMk=", - BinaryStringEncodingType.BASE64); - BinaryString initializationVector = new PlainString("🧅".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); - - PersistenceCipherType currentCipherType = PersistenceCipherType.AES_GCM_SIV_128; - PersistenceCipherType newCipherType = PersistenceCipherType.AES_GCM_SIV_256; - RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(expectedCipherText, newCipherText); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher()); - assertTrue(rotation.isSecretRotationStrategy()); - assertTrue(rotation.isCipherRotationStrategy()); - - PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret); - String plainText = cipher.decrypt(newCipherText, initializationVector); - - assertEquals(expectedPlainText, plainText); - } - - @Test - void cipher_and_secret_rotation_aes_gcm_siv_256_to_none() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlainText = "Hello 👋, welcome to 🌐."; - - BinaryString expectedCipherText = BinaryStringFactory.createFromString(expectedPlainText, BinaryStringEncodingType.PLAIN); - BinaryString currentSecret = new HexString("🥦🥕🥔🫘🥒🫑🌽🍆"); - BinaryString newSecret = new HexString("abc"); - BinaryString cipherText = BinaryStringFactory.createFromHex("d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6", - BinaryStringEncodingType.HEX); - BinaryString initializationVector = new Base64String("🧅".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4)); - - PersistenceCipherType currentCipherType = PersistenceCipherType.AES_GCM_SIV_256; - PersistenceCipherType newCipherType = PersistenceCipherType.NONE; - RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(expectedCipherText, newCipherText); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher()); - assertTrue(rotation.isSecretRotationStrategy()); - assertTrue(rotation.isCipherRotationStrategy()); - - PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret); - String plainText = cipher.decrypt(newCipherText, initializationVector); - - assertEquals(expectedPlainText, plainText); - } - - @Test - void initializationVector_rotation_cipher_none() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - BinaryString secret = new PlainString("abc"); - BinaryString cipherText = new PlainString("hello"); - BinaryString initializationVector = new PlainString("vi"); - BinaryString newInitializationVector = new PlainString("iv"); - - PersistenceCipherType cipherType = PersistenceCipherType.NONE; - RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(cipherText, newCipherText); - assertFalse(rotation.isSecretRotationStrategy()); - assertFalse(rotation.isCipherRotationStrategy()); - assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher()); - } - - @Test - void initializationVector_rotation_aes_gcm_siv_128() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlainText = "The quick brown fox jumps over the lazy dog."; - - BinaryString expectedCipherText = BinaryStringFactory - .createFromBase64("roRrChR9D/VIaebHZgxGG3tYuCR+yXjPleQr4bDZ7B5qsVzC7EnXTi3rT3qDv09t62W/W4V9CIfQRlWs"); - BinaryString secret = new PlainString("d".repeat(16)); - BinaryString cipherText = BinaryStringFactory.createFromBase64("h3Pz6Kue6V4CUYtkZdtWml0vmXkEXjSpXyud98Z3NJjNGk+qFYJmpLCoyO+qBxWKQfxbmnmup9+vjYJQ"); - BinaryString initializationVector = new HexString("vi".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); - BinaryString newInitializationVector = new Base64String("iv".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); - - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128; - RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(expectedCipherText, newCipherText); - assertFalse(rotation.isSecretRotationStrategy()); - assertFalse(rotation.isCipherRotationStrategy()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher()); - - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - String plainText = cipher.decrypt(newCipherText, newInitializationVector); - - assertEquals(expectedPlainText, plainText); - } - - @Test - void initializationVector_rotation_aes_gcm_siv_256() - throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { - /* prepare */ - String expectedPlainText = "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich"; - - BinaryString expectedCipherText = BinaryStringFactory - .createFromBase64("0eG35+7x+nr4MbsgRsTqOEjRReF6JzM2thWe+3zHleta1cCXzyCxQJ5Jaxeq7TnYDDg3nR/RcQdc00UlqEJw3WODaffeETDJ0bk5LfP19FWr/g=="); - BinaryString secret = new PlainString("ThisIsAStringWith32Characters!?!"); - BinaryString cipherText = BinaryStringFactory - .createFromBase64("AfVfrXdv8JImZ9qrpO7vOJRwveOaYRJNsQaeJiuDg5nX3ZT13EwH7CXlZa8IBNvhrqUvEn3w0L0J+JjYVYPxh80CqCpgjbMl4a8Ql5eyqKQIZQ=="); - BinaryString initializationVector = new HexString("vi".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); - BinaryString newInitializationVector = new Base64String("iv".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2)); - - PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256; - RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType); - - /* execute */ - BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector); - - /* test */ - assertNotNull(rotation); - assertEquals(expectedCipherText, newCipherText); - assertFalse(rotation.isSecretRotationStrategy()); - assertFalse(rotation.isCipherRotationStrategy()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher()); - assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher()); - - PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret); - String plainText = cipher.decrypt(newCipherText, newInitializationVector); - - assertEquals(expectedPlainText, plainText); - } -} diff --git a/sechub-commons-encryption/README.adoc b/sechub-commons-encryption/README.adoc new file mode 100644 index 0000000000..8846a51f5b --- /dev/null +++ b/sechub-commons-encryption/README.adoc @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +== About +`sechub-commons-encryption` is a library without any dependency to Spring or SecHub. + +It has only a dependency to bouncy castle library to provide security. + diff --git a/sechub-commons-encryption/build.gradle b/sechub-commons-encryption/build.gradle new file mode 100644 index 0000000000..ef6805e98c --- /dev/null +++ b/sechub-commons-encryption/build.gradle @@ -0,0 +1,11 @@ + // SPDX-License-Identifier: MIT +dependencies { + + implementation library.bouncy_castle_bcprov_jdk8 + + testImplementation spring_boot_dependency.junit_jupiter + testImplementation spring_boot_dependency.mockito_core + testImplementation spring_boot_dependency.assertj_core + +} + \ No newline at end of file diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/AesGcmSivCipher.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/AesGcmSivCipher.java new file mode 100644 index 0000000000..cd7e309cda --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/AesGcmSivCipher.java @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.Security; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +/** + * Providing access to AES-GCM-SIV + * + * AES-GCM-SIV is a nonce misuse-resistant authenticated encryption algorithm. + * + * For more information refer to + * RFC 8452 + * + * @author Jeremias Eppler + * @author Albert Tregnaghi + */ +class AesGcmSivCipher implements PersistentCipher { + + private static BouncyCastleProvider cryptoProvider; + + public static final int AUTHENTICATION_TAG_LENGTH_IN_BITS = 16 * 8; // 16 bytes (128 bits) + + /** + * The recommended initialization vector (iv) for AES-GCM-SIV is 12 bytes or 96 + * bits. + * + * For an explanation have a look at: - + * https://datatracker.ietf.org/doc/html/rfc8452#section-4 - + * https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes + */ + static final int IV_LENGTH_IN_BYTES = 12; + + private static final String ALGORITHM = "AES/GCM-SIV/NoPadding"; + + static { + cryptoProvider = new BouncyCastleProvider(); + + Security.addProvider(cryptoProvider); + } + + private SecretKey secretKey; + + private PersistentCipherType type; + + AesGcmSivCipher(SecretKey secretKey, PersistentCipherType type) { + this.secretKey = secretKey; + this.type = type; + } + + @Override + public byte[] encrypt(byte[] origin, InitializationVector initVector) { + + try { + Cipher cipher = Cipher.getInstance(ALGORITHM, cryptoProvider); + + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initVector.getInitializationBytes()); + + cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec); + + byte[] encrypted = cipher.doFinal(origin); + + return encrypted; + + } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) { + throw new IllegalStateException("Encryption not possible, please check the provider", providerException); + } catch (BadPaddingException | IllegalBlockSizeException paddingBlockException) { + throw new IllegalStateException("Should not occur. AES in GCM-SIV mode does not require padding.", paddingBlockException); + } catch (InvalidKeyException e) { + throw new IllegalStateException("Key not valid", e); + } catch (InvalidAlgorithmParameterException e) { + throw new IllegalStateException("Invalid algorithm parameters", e); + } + + } + + @Override + public byte[] decrypt(byte[] encryptedData, InitializationVector initVector) { + Cipher cipher; + try { + cipher = Cipher.getInstance(ALGORITHM, cryptoProvider); + + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initVector.getInitializationBytes()); + + cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); + + byte[] plaintextBytes = cipher.doFinal(encryptedData); + return plaintextBytes; + } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) { + throw new IllegalStateException("Decryption not possible, please check the provider", providerException); + } catch (InvalidKeyException e) { + throw new IllegalStateException("Key not valid", e); + } catch (InvalidAlgorithmParameterException e) { + throw new IllegalStateException("Invalid algorithm parameters", e); + } catch (IllegalBlockSizeException e) { + throw new IllegalStateException(e); + } catch (BadPaddingException e) { + throw new IllegalStateException(e); + } + } + + @Override + public InitializationVector createNewInitializationVector() { + + byte[] initializationVector = new byte[IV_LENGTH_IN_BYTES]; + + SecureRandom random = new SecureRandom(); + random.nextBytes(initializationVector); + + return new InitializationVector(initializationVector); + } + + @Override + public PersistentCipherType getType() { + return type; + } + + @Override + public String toString() { + return type == null ? "Unknown " + getClass().getSimpleName() + " type!" : type.name(); + } + +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/ByteArrayTransformer.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/ByteArrayTransformer.java new file mode 100644 index 0000000000..73e19ef125 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/ByteArrayTransformer.java @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +/** + * Byte array transformer interface. Implementations are able to transform + * encrypted data (which we always handle as bytes) into an object type and to + * transform the object type to a byte array. + * + * @author Albert Tregnaghi + * + * @param + */ +public interface ByteArrayTransformer { + + /** + * Transform given bytes to target object. If bytes array is null, the object + * will be null + * + * @param bytes + * @return created object + */ + public T transformFromBytes(byte[] bytes); + + /** + * Transforms given object to byte array. If object is null, + * null will be returned + * + * @param object the object to transform (can be null) + * @return byte array, never null + */ + public byte[] transformToBytes(T object); + +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/DefaultSecretKeyProvider.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/DefaultSecretKeyProvider.java new file mode 100644 index 0000000000..09f8125647 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/DefaultSecretKeyProvider.java @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +/** + * Default implementation of a secret key provider. + * + * @author Albert Tregnaghi + * + */ +public class DefaultSecretKeyProvider implements SecretKeyProvider { + + private int lengthInBits; + private SecretKey secretKey; + + public DefaultSecretKeyProvider(byte[] rawSecret, PersistentCipherType cipherType) { + if (rawSecret == null) { + throw new IllegalArgumentException("secret may not be null"); + } + if (rawSecret.length == 0) { + throw new IllegalArgumentException("secret bytes array may not be empty"); + } + if (cipherType == null) { + throw new IllegalArgumentException("cipher type not defined"); + } + String secretKeyAlgorithm = cipherType.getSecretKeyAlgorithm(); + if (secretKeyAlgorithm == null || secretKeyAlgorithm.isBlank()) { + throw new IllegalArgumentException("cipher type: " + cipherType + " does not provide an algorithm for secret keys!"); + } + + lengthInBits = rawSecret.length * 8; + + secretKey = new SecretKeySpec(rawSecret, 0, rawSecret.length, secretKeyAlgorithm); + } + + @Override + public SecretKey getSecretKey() { + return secretKey; + } + + @Override + public int getLengthOfSecretInBits() { + return lengthInBits; + } + +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionConstants.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionConstants.java new file mode 100644 index 0000000000..8f22ba7655 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionConstants.java @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import java.nio.charset.Charset; + +public class EncryptionConstants { + + public static final Charset UTF8_CHARSET_ENCODING = Charset.forName("UTF-8"); +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionResult.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionResult.java new file mode 100644 index 0000000000..00cb98f84b --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionResult.java @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +/** + * Represents the result of an encryption. Contains encrypted data but also + * initial vector. + * + * @author Albert Tregnaghi + * + */ +public class EncryptionResult { + + private byte[] encryptedData; + + private InitializationVector initialVector; + + public EncryptionResult(byte[] encryptedData, InitializationVector initialVector) { + if (initialVector == null) { + throw new IllegalArgumentException("initial vector may not be null!"); + } + this.encryptedData = encryptedData; + this.initialVector = initialVector; + } + + /** + * @return encrypted data or null + */ + public byte[] getEncryptedData() { + return encryptedData; + } + + /** + * @return initial vector, never null + */ + public InitializationVector getInitialVector() { + return initialVector; + } + +} \ No newline at end of file diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotationSetup.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotationSetup.java new file mode 100644 index 0000000000..92e385bda2 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotationSetup.java @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +public class EncryptionRotationSetup { + + public static RotationSetupBuilder builder() { + return new RotationSetupBuilder(); + } + + /* private, because MUST be created by builder - which ensures setup is valid */ + private EncryptionRotationSetup() { + + } + + private PersistentCipher oldCipher; + private InitializationVector oldInitialVector; + private PersistentCipher newCipher; + private InitializationVector newInitialVector; + + public PersistentCipher getOldCipher() { + return oldCipher; + } + + public InitializationVector getOldInitialVector() { + return oldInitialVector; + } + + public PersistentCipher getNewCipher() { + return newCipher; + } + + public InitializationVector getNewInitialVector() { + return newInitialVector; + } + + public static class RotationSetupBuilder { + + private PersistentCipher oldCipher; + private InitializationVector oldInitialVector; + private PersistentCipher newCipher; + private InitializationVector newInitialVector; + + private RotationSetupBuilder() { + } + + public RotationSetupBuilder oldInitialVector(InitializationVector oldInitialVector) { + this.oldInitialVector = oldInitialVector; + return this; + } + + public RotationSetupBuilder newInitialVector(InitializationVector newInitialVector) { + this.newInitialVector = newInitialVector; + return this; + } + + public RotationSetupBuilder oldCipher(PersistentCipher oldCipher) { + this.oldCipher = oldCipher; + return this; + } + + public RotationSetupBuilder newCipher(PersistentCipher newCipher) { + this.newCipher = newCipher; + return this; + } + + /** + * Builds a new rotation data object. If the new initial vector is not set, the + * old initial vector will be used If the new cipher is not set, the old cipher + * will be used. But if none of them ( no new initial vector and no new cipher) + * is defined an {@link IllegalArgumentException} will be thrown (because setup + * provides no rotation at all) + * + * @return new rotation data object + * @throws IllegalArgumentException when no new cipher and no new initial vector + * defined + * @throws IllegalArgumentException when no old cipher defined + * @throws IllegalArgumentException when no old initial vector defined + */ + public EncryptionRotationSetup build() { + if (oldCipher == null) { + throw new IllegalArgumentException("old cipher must be defined in builder before build is called!"); + } + if (oldInitialVector == null) { + throw new IllegalArgumentException("old initial vector must be defined in builder before build is called!"); + } + + if (newInitialVector == null && newCipher == null) { + throw new IllegalArgumentException("no new cipher or a new initial vector given - rotation is impossible in this case!"); + } + + EncryptionRotationSetup data = new EncryptionRotationSetup(); + data.oldCipher = this.oldCipher; + data.oldInitialVector = this.oldInitialVector; + + if (newCipher == null) { + data.newCipher = this.oldCipher; + } else { + data.newCipher = this.newCipher; + } + + if (newInitialVector == null) { + data.newInitialVector = this.oldInitialVector; + } else { + data.newInitialVector = this.newInitialVector; + } + + return data; + } + } +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotator.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotator.java new file mode 100644 index 0000000000..0d38baaca5 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotator.java @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +public class EncryptionRotator { + + /** + * Rotates encryption for existing encrypted data. + * + * @param encryptedData data which is encrypted and shall be rotated + * @param setup rotation setup to use, may not be null + * @return new encrypted data + */ + public byte[] rotate(byte[] encryptedData, EncryptionRotationSetup setup) { + if (setup == null) { + throw new IllegalArgumentException("NO rotation setup defined"); + } + // no further checks necessary - is don by rotation setup builder which is the + // only way to create such an object + + byte[] unencryptedData = setup.getOldCipher().decrypt(encryptedData, setup.getOldInitialVector()); + byte[] newEncryptedData = setup.getNewCipher().encrypt(unencryptedData, setup.getNewInitialVector()); + + return newEncryptedData; + } + +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionSupport.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionSupport.java new file mode 100644 index 0000000000..668b58839b --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/EncryptionSupport.java @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +/** + * A class to simplify encryption (and also decryption) + * + * @author Albert Tregnaghi + * + */ +public class EncryptionSupport { + + private StringByteArrayTransformer stringTransformer = new StringByteArrayTransformer(); + + /** + * Encrypt given string + * + * @param string text to encrypt + * @param cipher cipher to use for encryption + * @return {@link EncryptionResult}, never null + */ + public EncryptionResult encryptString(String string, PersistentCipher cipher) { + return encrypt(string, cipher, stringTransformer); + } + + /** + * Encrypt given object. Will create new initial vector automatically. + * + * @param target object type + * @param object object to encrypt + * @param cipher cipher to use for encryption + * @param transformer transformer used to transform object into a byte array + * @return {@link EncryptionResult}, never null + */ + public EncryptionResult encrypt(T object, PersistentCipher cipher, ByteArrayTransformer transformer) { + if (transformer == null) { + throw new IllegalArgumentException("transformer may not be null!"); + } + byte[] transformToBytes = transformer.transformToBytes(object); + + InitializationVector initialVector = cipher.createNewInitializationVector(); + + byte[] encrypted = cipher.encrypt(transformToBytes, initialVector); + + EncryptionResult result = new EncryptionResult(encrypted, initialVector); + return result; + } + + /** + * Decrypts given encrypted byte array and automatically transform to a string + * by using a {@link StringByteArrayTransformer}. + * + * @param encryptedData + * @param cipher + * @param initialVector + * @return string or null + */ + public String decryptString(byte[] encryptedData, PersistentCipher cipher, InitializationVector initialVector) { + return decrypt(encryptedData, cipher, initialVector, stringTransformer); + } + + /** + * Decrypts given encrypted byte array via given byte array transformer instance + * + * @param Target object type + * @param encryptedData byte array containing encrypted dta + * @param cipher cipher to use + * @param initialVector initial vector to use + * @param transformer transformer which will be used to transform byte array + * result from cipher instance to target object + * @return target object or null + */ + public T decrypt(byte[] encryptedData, PersistentCipher cipher, InitializationVector initialVector, ByteArrayTransformer transformer) { + if (transformer == null) { + throw new IllegalArgumentException("transformer may not be null!"); + } + byte[] unencrypted = cipher.decrypt(encryptedData, initialVector); + + return transformer.transformFromBytes(unencrypted); + } + +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/InitializationVector.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/InitializationVector.java new file mode 100644 index 0000000000..0591f4b409 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/InitializationVector.java @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import java.util.Arrays; + +public class InitializationVector { + + private byte[] initializationBytes; + + public InitializationVector(byte[] bytes) { + this.initializationBytes = bytes; + } + + public byte[] getInitializationBytes() { + return initializationBytes; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "#" + hashCode(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(initializationBytes); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + InitializationVector other = (InitializationVector) obj; + return Arrays.equals(initializationBytes, other.initializationBytes); + } +} \ No newline at end of file diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/NoneCipher.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/NoneCipher.java new file mode 100644 index 0000000000..9de25eb6dc --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/NoneCipher.java @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +class NoneCipher implements PersistentCipher { + + private static final InitializationVector INITIAL_VECTOR = new InitializationVector("none".getBytes(EncryptionConstants.UTF8_CHARSET_ENCODING)); + + @Override + public byte[] encrypt(byte[] plainData, InitializationVector initVector) { + return plainData; + } + + @Override + public byte[] decrypt(byte[] encryptedData, InitializationVector initVector) { + return encryptedData; + } + + @Override + public InitializationVector createNewInitializationVector() { + /* + * We always use the "same" initial vector which is always "none". Means it is + * also clear in database that this is not a real initial vector. The NoneCipher + * implementation does need an initial vector at all... but having a database + * column and having this method inside the interface contract, it is necessary + * to provide a value. + */ + return INITIAL_VECTOR; + } + + @Override + public PersistentCipherType getType() { + return PersistentCipherType.NONE; + } + + @Override + public String toString() { + return getType().name(); + } +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipher.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipher.java new file mode 100644 index 0000000000..bc24374309 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipher.java @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +public interface PersistentCipher { + + /** + * Encrypts plain data with given initial vector + * + * @param data the origin (not encrypted) data + * @param initialVector an initial vector + * @return encrypted data as byte array + */ + public byte[] encrypt(byte[] data, InitializationVector initialVector); + + /** + * Decrypts given encrypted data with initial vector + * + * @param encryptedData the encrypted data + * @param initialVector an initial vector to use for decryption + * @return decrypted data as byte array + */ + public byte[] decrypt(byte[] encryptedData, InitializationVector initialVector); + + /** + * Creates a new initialization vector which can be used for encryption. Remark: + * The bytes of the initialization vector must be stored together with the + * encrypted data. Otherwise it is not possible to retain the origin data even + * when the secret key is known! + * + * @return initialization vector which provides initialization bytes, never + * null + */ + public InitializationVector createNewInitializationVector(); + + /** + * @return the type of the cipher + */ + public PersistentCipherType getType(); +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherFactory.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherFactory.java new file mode 100644 index 0000000000..89d8fb68b2 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherFactory.java @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +public class PersistentCipherFactory { + + public PersistentCipher createCipher(SecretKeyProvider secretProvider, PersistentCipherType type) { + + if (secretProvider == null && !PersistentCipherType.NONE.equals(type)) { + throw new IllegalArgumentException("secret key provider must be defined for all cipher types (except NONE)"); + } + + if (type == null) { + throw new IllegalArgumentException("cipher type must be defined!"); + } + + switch (type) { + case AES_GCM_SIV_128: + assertKeyHasBits(secretProvider, type, 128); + return new AesGcmSivCipher(secretProvider.getSecretKey(), type); + case AES_GCM_SIV_256: + assertKeyHasBits(secretProvider, type, 256); + return new AesGcmSivCipher(secretProvider.getSecretKey(), type); + case NONE: + return new NoneCipher(); + default: + throw new IllegalStateException("There is no implementation for %s".formatted(type)); + } + } + + private static void assertKeyHasBits(SecretKeyProvider secretProvider, PersistentCipherType type, int bitsWanted) { + int amountOfBits = secretProvider.getLengthOfSecretInBits(); + if (amountOfBits != bitsWanted) { + throw new IllegalArgumentException("The type %s does only accept %s bits for secret key, but returned secret key by provider had %s bit!" + .formatted(type, bitsWanted, amountOfBits)); + } + } + +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherType.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherType.java new file mode 100644 index 0000000000..c9ddd68f82 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherType.java @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +public enum PersistentCipherType { + + NONE(null), + + AES_GCM_SIV_128("AES"), + + AES_GCM_SIV_256("AES"), + + ; + + private String secretKeyAlgorithm; + + /** + * Creates a new cipher type + * + * @param secretKeyAlgorithm the algorithm which shall be used for secret key + * creation + */ + private PersistentCipherType(String secretKeyAlgorithm) { + this.secretKeyAlgorithm = secretKeyAlgorithm; + } + + String getSecretKeyAlgorithm() { + return secretKeyAlgorithm; + } + +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/SecretKeyProvider.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/SecretKeyProvider.java new file mode 100644 index 0000000000..395b6f8ff1 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/SecretKeyProvider.java @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import javax.crypto.SecretKey; + +public interface SecretKeyProvider { + + public int getLengthOfSecretInBits(); + + public SecretKey getSecretKey(); +} diff --git a/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/StringByteArrayTransformer.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/StringByteArrayTransformer.java new file mode 100644 index 0000000000..43d85fc347 --- /dev/null +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/StringByteArrayTransformer.java @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +/** + * A specialized byte transformer which transform String to byte array a vice + * versa with same character encoding (UTF-8). + * + * @author Albert Tregnaghi + * + */ +public class StringByteArrayTransformer implements ByteArrayTransformer { + + @Override + public String transformFromBytes(byte[] bytes) { + if (bytes == null) { + return null; + } + return new String(bytes, EncryptionConstants.UTF8_CHARSET_ENCODING); + } + + @Override + public byte[] transformToBytes(String string) { + if (string == null) { + return null; + } + return string.getBytes(EncryptionConstants.UTF8_CHARSET_ENCODING); + } + +} diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/package-info.java similarity index 93% rename from sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java rename to sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/package-info.java index a666fada63..b6913f3696 100644 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java +++ b/sechub-commons-encryption/src/main/java/com/mercedesbenz/sechub/commons/encryption/package-info.java @@ -2,9 +2,6 @@ /** * This package contains classes to protect data at-rest. * - * The package is named persistence, because the persistence layer is used to - * help store data in a database or other data storage systems. - * * The requirements for the cryptographic algorithms for data at rest are: * *
    @@ -77,4 +74,4 @@ * * @author Jeremias Eppler */ -package com.mercedesbenz.sechub.commons.core.security.persistence; \ No newline at end of file +package com.mercedesbenz.sechub.commons.encryption; \ No newline at end of file diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/AesGcmSivCipherTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/AesGcmSivCipherTest.java new file mode 100644 index 0000000000..c8e6370c2b --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/AesGcmSivCipherTest.java @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.junit.jupiter.api.Assertions.*; + +import javax.crypto.spec.SecretKeySpec; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class AesGcmSivCipherTest { + + private SecretKeySpec aes256secretKey; + private SecretKeySpec aes128secretKey; + + @BeforeEach + void beforeEach() { + /* prepare */ + byte[] aes256pwd = "a".repeat(32).getBytes(); + aes256secretKey = new SecretKeySpec(aes256pwd, "AES"); + + byte[] aes128pwd = "a".repeat(16).getBytes(); + aes128secretKey = new SecretKeySpec(aes128pwd, "AES"); + } + + @Test + void aes_gcm_siv_256_encryption_and_decryption_works_in_general() { + + /* prepare */ + AesGcmSivCipher cipherToTest = new AesGcmSivCipher(aes256secretKey, PersistentCipherType.AES_GCM_SIV_256); + + InitializationVector initVector = cipherToTest.createNewInitializationVector(); + byte[] initVectorInBytes = initVector.getInitializationBytes(); + byte[] dataToEncrypt = "i am the plain text :-)".getBytes(); + + /* execute */ + byte[] encryptedBytes = cipherToTest.encrypt(dataToEncrypt, initVector); + + /* test */ + AesGcmSivCipher cipherFromOutside = new AesGcmSivCipher(aes256secretKey, PersistentCipherType.AES_GCM_SIV_256); + byte[] decrypted = cipherFromOutside.decrypt(encryptedBytes, new InitializationVector(initVectorInBytes)); + + assertEquals(new String(dataToEncrypt), new String(decrypted)); + assertNotEquals(new String(encryptedBytes), new String(dataToEncrypt)); + + } + + @Test + void aes_gcm_siv_256_initialization_cipher_has_expected_length() { + + /* prepare */ + AesGcmSivCipher cipherToTest = new AesGcmSivCipher(aes256secretKey, PersistentCipherType.AES_GCM_SIV_256); + + /* execute */ + InitializationVector initVector = cipherToTest.createNewInitializationVector(); + + /* test */ + assertEquals(AesGcmSivCipher.IV_LENGTH_IN_BYTES, initVector.getInitializationBytes().length); + + } + + @Test + void aes_gcm_siv_128_initialization_cipher_has_expected_length() { + + /* prepare */ + AesGcmSivCipher cipherToTest = new AesGcmSivCipher(aes128secretKey, PersistentCipherType.AES_GCM_SIV_128); + + /* execute */ + InitializationVector initVector = cipherToTest.createNewInitializationVector(); + + /* test */ + assertEquals(AesGcmSivCipher.IV_LENGTH_IN_BYTES, initVector.getInitializationBytes().length); + + } + + @Test + void aes_gcm_siv_128_encryption_and_decryption_works_in_general() { + + /* prepare */ + AesGcmSivCipher cipherToTest = new AesGcmSivCipher(aes128secretKey, PersistentCipherType.AES_GCM_SIV_128); + + InitializationVector initVector = cipherToTest.createNewInitializationVector(); + byte[] initVectorInBytes = initVector.getInitializationBytes(); + byte[] dataToEncrypt = "i am the plain text :-)".getBytes(); + + /* execute */ + byte[] encryptedBytes = cipherToTest.encrypt(dataToEncrypt, initVector); + + /* test */ + AesGcmSivCipher cipherFromOutside = new AesGcmSivCipher(aes128secretKey, PersistentCipherType.AES_GCM_SIV_128); + byte[] decrypted = cipherFromOutside.decrypt(encryptedBytes, new InitializationVector(initVectorInBytes)); + + assertEquals(new String(dataToEncrypt), new String(decrypted)); + assertNotEquals(new String(encryptedBytes), new String(dataToEncrypt)); + + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/DefaultSecretKeyProviderTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/DefaultSecretKeyProviderTest.java new file mode 100644 index 0000000000..5df56054ad --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/DefaultSecretKeyProviderTest.java @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.random.RandomGenerator; +import java.util.random.RandomGeneratorFactory; + +import javax.crypto.SecretKey; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class DefaultSecretKeyProviderTest { + + private static RandomGenerator randomGenerator; + + @BeforeAll + static void beforeAll() { + randomGenerator = RandomGeneratorFactory.getDefault().create(); + } + + @ParameterizedTest + @NullSource + @EmptySource + void illegal_arguments_detected(byte[] secret) { + + assertThrows(IllegalArgumentException.class, () -> new DefaultSecretKeyProvider(secret, PersistentCipherType.AES_GCM_SIV_128)); + assertThrows(IllegalArgumentException.class, () -> new DefaultSecretKeyProvider(secret, PersistentCipherType.AES_GCM_SIV_256)); + } + + @ParameterizedTest + @ValueSource(ints = { 1, 16, 32, 64 }) + void secretKey_in_bytes_is_correct_created(int secretByteLength) { + + /* prepare */ + byte[] rawTestSecret = createSecretInBytes(secretByteLength); + + /* execute */ + DefaultSecretKeyProvider providerToTest = new DefaultSecretKeyProvider(rawTestSecret, PersistentCipherType.AES_GCM_SIV_256); + + /* test */ + assertEquals(secretByteLength * 8, providerToTest.getLengthOfSecretInBits()); + + SecretKey secretKey = providerToTest.getSecretKey(); + assertNotNull(secretKey); + + byte[] encoded = secretKey.getEncoded(); + assertThat(encoded).isEqualTo(rawTestSecret); + } + + private byte[] createSecretInBytes(int secretKeyInBytes) { + byte[] randomSecret = new byte[secretKeyInBytes]; + randomGenerator.nextBytes(randomSecret); + return randomSecret; + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotationSetupTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotationSetupTest.java new file mode 100644 index 0000000000..9b4ba0b143 --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotationSetupTest.java @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Rotation setup test will test also the builder because the setup cannot be + * created without the builder and the builder has logic inside on build time + * which must be tested as well. + * + * @author Albert Tregnaghi + * + */ +class EncryptionRotationSetupTest { + + private PersistentCipher cipher1; + private InitializationVector initialVector1; + + private PersistentCipher cipher2; + private InitializationVector initialVector2; + + @BeforeEach + void beforeEach() { + cipher1 = mock(PersistentCipher.class); + cipher2 = mock(PersistentCipher.class); + + initialVector1 = mock(InitializationVector.class); + initialVector2 = mock(InitializationVector.class); + } + + @Test + void setup_build_without_arguments_throws_exception() { + /* @formatter:off */ + assertThatThrownBy( + ()-> + EncryptionRotationSetup.builder(). + build()). + + isInstanceOf(IllegalArgumentException.class). + hasMessageStartingWith("old cipher must be defined"); + /* @formatter:on */ + } + + @Test + void setup_build_without_old_cipher_throws_exception() { + /* @formatter:off */ + assertThatThrownBy( + ()-> + EncryptionRotationSetup.builder(). + oldInitialVector(initialVector1). + build()). + + isInstanceOf(IllegalArgumentException.class). + hasMessageStartingWith("old cipher must be defined"); + /* @formatter:on */ + } + + @Test + void setup_build_without_old_initialvector_throws_exception() { + /* @formatter:off */ + assertThatThrownBy( + ()-> + EncryptionRotationSetup.builder(). + oldCipher(cipher1). + build()). + + isInstanceOf(IllegalArgumentException.class). + hasMessageStartingWith("old initial vector must be defined"); + /* @formatter:on */ + } + + @Test + void setup_build_throws_exception_when_only_old_cipher_and_old_initial_vector() { + /* @formatter:off */ + assertThatThrownBy( + ()-> + EncryptionRotationSetup.builder(). + oldCipher(cipher1). + oldInitialVector(initialVector1). + build()). + + isInstanceOf(IllegalArgumentException.class). + hasMessageStartingWith("no new cipher or a new initial vector given"); + /* @formatter:on */ + } + + @Test + void setup_build_throws_exception_when_only_new_cipher_and_new_initial_vector() { + /* @formatter:off */ + assertThatThrownBy( + ()-> + EncryptionRotationSetup.builder(). + newCipher(cipher1). + newInitialVector(initialVector1). + build()). + + isInstanceOf(IllegalArgumentException.class). + hasMessageStartingWith("old cipher must be defined"); + /* @formatter:on */ + } + + @Test + void setup_build_throws_exception_when_old_initial_vector_is_missing() { + /* @formatter:off */ + assertThatThrownBy( + ()-> + EncryptionRotationSetup.builder(). + newCipher(cipher1). + newInitialVector(initialVector1). + oldCipher(cipher2). + build()). + + isInstanceOf(IllegalArgumentException.class). + hasMessageStartingWith("old initial vector must be defined"); + /* @formatter:on */ + } + + @Test + void setup_build_throws_exception_when_old_cipher_is_missing() { + /* @formatter:off */ + assertThatThrownBy( + ()-> + EncryptionRotationSetup.builder(). + newCipher(cipher1). + newInitialVector(initialVector1). + oldInitialVector(initialVector2). + build()). + + isInstanceOf(IllegalArgumentException.class). + hasMessageStartingWith("old cipher must be defined"); + /* @formatter:on */ + } + + @Test + void setup_build_works_for_password_rotation_when_no_new_initial_vector_is_set() { + /* @formatter:off */ + + /* execute */ + EncryptionRotationSetup setup = EncryptionRotationSetup.builder(). + oldCipher(cipher1). + newCipher(cipher2). + oldInitialVector(initialVector1). + build(); + + + /* test */ + assertThat(setup).isNotNull(); + assertThat(setup.getOldCipher()).isEqualTo(cipher1); + assertThat(setup.getNewCipher()).isEqualTo(cipher2); + assertThat(setup.getOldInitialVector()).isEqualTo(initialVector1); + // no new initial vector set, builder will setup to old one automatically + assertThat(setup.getNewInitialVector()).isEqualTo(initialVector1); + + /* @formatter:on */ + } + + @Test + void setup_build_works_for_initial_vector_rotation_when_no_new_cipher_is_set() { + /* @formatter:off */ + + /* execute */ + EncryptionRotationSetup setup = EncryptionRotationSetup.builder(). + oldCipher(cipher1). + oldInitialVector(initialVector1). + newInitialVector(initialVector2). + build(); + + + /* test */ + assertThat(setup).isNotNull(); + assertThat(setup.getOldInitialVector()).isEqualTo(initialVector1); + assertThat(setup.getNewInitialVector()).isEqualTo(initialVector2); + assertThat(setup.getOldCipher()).isEqualTo(cipher1); + + // no new cipher set, builder will setup to old one automatically + assertThat(setup.getNewCipher()).isEqualTo(cipher1); + /* @formatter:on */ + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotatorTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotatorTest.java new file mode 100644 index 0000000000..0feacdcb5f --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/EncryptionRotatorTest.java @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class EncryptionRotatorTest { + + private EncryptionRotator rotatorToTest; + private EncryptionRotationSetup rotationSetup; + private PersistentCipher oldCipher; + private PersistentCipher newCipher; + private InitializationVector oldInitialVector; + private InitializationVector newInitialVector; + private byte[] plainTextAsBytes; + private byte[] newEncryptedData; + private byte[] oldEncryptedData; + + @BeforeEach + void beforeEach() { + plainTextAsBytes = "testdata".getBytes(); + oldEncryptedData = "old-encrypted".getBytes(); + newEncryptedData = "new-encrypted".getBytes(); + + rotatorToTest = new EncryptionRotator(); + rotationSetup = mock(EncryptionRotationSetup.class); + + oldCipher = mock(PersistentCipher.class); + newCipher = mock(PersistentCipher.class); + + oldInitialVector = mock(InitializationVector.class); + newInitialVector = mock(InitializationVector.class); + + when(rotationSetup.getOldCipher()).thenReturn(oldCipher); + when(rotationSetup.getNewCipher()).thenReturn(newCipher); + when(rotationSetup.getOldInitialVector()).thenReturn(oldInitialVector); + when(rotationSetup.getNewInitialVector()).thenReturn(newInitialVector); + + when(oldCipher.decrypt(oldEncryptedData, oldInitialVector)).thenReturn(plainTextAsBytes); + when(newCipher.encrypt(plainTextAsBytes, newInitialVector)).thenReturn(newEncryptedData); + } + + @Test + void roation_uses_old_cipher_and_initvector_to_decrypt() { + + /* execute */ + rotatorToTest.rotate(oldEncryptedData, rotationSetup); + + /* test */ + verify(oldCipher).decrypt(oldEncryptedData, oldInitialVector); + + } + + @Test + void roation_uses_new_cipher_and_new_initvector_to_encrypt() { + + /* execute */ + rotatorToTest.rotate(oldEncryptedData, rotationSetup); + + /* test */ + verify(newCipher).encrypt(plainTextAsBytes, newInitialVector); + + } + + @Test + void roation_result_is_new_encrypted_data() { + + /* execute */ + byte[] result = rotatorToTest.rotate(oldEncryptedData, rotationSetup); + + /* test */ + assertThat(result).isEqualTo(newEncryptedData); + + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/FullEncryptionRotationTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/FullEncryptionRotationTest.java new file mode 100644 index 0000000000..2215b8a54c --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/FullEncryptionRotationTest.java @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.stream.Stream; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +/** + * This class tests not only the rotator, but also other parts of the encryption + * library. It uses real ciphers which are generated by the cipher factory etc. + * etc. + * + * So it is not a normal unit tests for the rotator but more an integration test + * of the encryption library! + * + * @author Albert Tregnaghi + * + */ +class FullEncryptionRotationTest { + + private static PersistentCipher cipher1Aes256; + private static PersistentCipher cipher2Aes256; + private static PersistentCipher cipher3Aes128; + private static PersistentCipher cipher4Aes128; + private static PersistentCipher cipher5None; + private static PersistentCipher cipher6None; + private static InitializationVector initialVector1aes256; + private static InitializationVector initialVector2aes256; + private static InitializationVector initialVector3None; + private static InitializationVector initialVector3aes128; + private EncryptionRotator rotatorToTest; + + @BeforeAll + static void beforeAll() { + PersistentCipherFactory factory = new PersistentCipherFactory(); + PersistentCipherType aes256CipherType = PersistentCipherType.AES_GCM_SIV_256; + cipher1Aes256 = factory.createCipher(new DefaultSecretKeyProvider("x".repeat(32).getBytes(), aes256CipherType), aes256CipherType); + cipher2Aes256 = factory.createCipher(new DefaultSecretKeyProvider("y".repeat(32).getBytes(), aes256CipherType), aes256CipherType); + + PersistentCipherType aes128CipherType = PersistentCipherType.AES_GCM_SIV_128; + cipher3Aes128 = factory.createCipher(new DefaultSecretKeyProvider("x".repeat(16).getBytes(), aes128CipherType), aes128CipherType); + cipher4Aes128 = factory.createCipher(new DefaultSecretKeyProvider("y".repeat(16).getBytes(), aes128CipherType), aes128CipherType); + + cipher5None = factory.createCipher(null, PersistentCipherType.NONE); + cipher6None = factory.createCipher(null, PersistentCipherType.NONE); + + initialVector1aes256 = cipher1Aes256.createNewInitializationVector(); + initialVector2aes256 = cipher2Aes256.createNewInitializationVector(); + initialVector3aes128 = cipher2Aes256.createNewInitializationVector(); + initialVector3None = cipher5None.createNewInitializationVector(); + + } + + @BeforeEach + void beforeEach() { + rotatorToTest = new EncryptionRotator(); + } + + @Test + void secret_rotation_cipher_none() + throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { + /* prepare */ + byte[] rawPlainTextBytes = "hello".getBytes(EncryptionConstants.UTF8_CHARSET_ENCODING); + + NoneCipher noneCipher1 = new NoneCipher(); + InitializationVector vector1 = noneCipher1.createNewInitializationVector(); + NoneCipher noneCipher2 = new NoneCipher(); + + /* @formatter:off */ + EncryptionRotationSetup setup = EncryptionRotationSetup.builder(). + oldCipher(noneCipher1). + oldInitialVector(vector1). + newCipher(noneCipher2). + build(); + + /* @formatter:on */ + + /* execute */ + byte[] result = rotatorToTest.rotate(rawPlainTextBytes, setup); + + /* test */ + assertArrayEquals(rawPlainTextBytes, result); + } + + @ParameterizedTest + @ArgumentsSource(DualCipherSameInitVectorArgumentsProvider.class) + void cipher_rotation_same_initial_vector(String testInfo, PersistentCipher cipher1, PersistentCipher cipher2, InitializationVector initVector) { + /* prepare */ + String textToEncrypt = "Hello, I am ☺️ 4 you."; + byte[] rawBytesToEncrypt = textToEncrypt.getBytes(EncryptionConstants.UTF8_CHARSET_ENCODING); + + byte[] encryptedWithCipher1 = cipher1.encrypt(rawBytesToEncrypt, initVector); + + /* execute */ + /* @formatter:off */ + EncryptionRotationSetup setup = EncryptionRotationSetup.builder(). + oldCipher(cipher1). + oldInitialVector(initVector). + newCipher(cipher2). + build(); + + /* @formatter:on */ + byte[] encryptedAfterRotation = rotatorToTest.rotate(encryptedWithCipher1, setup); + + // sanity check for this test: the encryption may not be same... + assertThat(encryptedAfterRotation).isNotEqualTo(encryptedWithCipher1); + + /* test */ + byte[] decryptedWithCipher2AfterRotation = cipher2.decrypt(encryptedAfterRotation, initVector); + + assertThat(rawBytesToEncrypt).isEqualTo(decryptedWithCipher2AfterRotation); + + } + + @ParameterizedTest + @ArgumentsSource(DualCipherDifferentInitVectorArgumentsProvider.class) + void cipher_rotation_different_initial_vector_and_different_cipher(String testInfo, PersistentCipher cipher1, PersistentCipher cipher2, + InitializationVector initVector1, InitializationVector initVector2) { + /* prepare */ + String textToEncrypt = "Hello, I am ☺️ 4 you."; + byte[] rawBytesToEncrypt = textToEncrypt.getBytes(EncryptionConstants.UTF8_CHARSET_ENCODING); + + byte[] encryptedWithCipher1 = cipher1.encrypt(rawBytesToEncrypt, initVector1); + + /* execute */ + /* @formatter:off */ + EncryptionRotationSetup setup = EncryptionRotationSetup.builder(). + oldCipher(cipher1). + oldInitialVector(initVector1). + newCipher(cipher2). + newInitialVector(initVector2). + build(); + + /* @formatter:on */ + byte[] encryptedAfterRotation = rotatorToTest.rotate(encryptedWithCipher1, setup); + + // sanity check for this test: the encryption may not be same... + assertThat(encryptedAfterRotation).isNotEqualTo(encryptedWithCipher1); + + /* test */ + byte[] decryptedWithCipher2AfterRotation = cipher2.decrypt(encryptedAfterRotation, initVector2); + + assertThat(rawBytesToEncrypt).isEqualTo(decryptedWithCipher2AfterRotation); + + } + + private static class DualCipherSameInitVectorArgumentsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + /* @formatter:off */ + + return Stream.of( + Arguments.of("cipher1(aes256)->2(aes256)", cipher1Aes256, cipher2Aes256, initialVector1aes256), + Arguments.of("cipher1(aes256)->3(aes128)", cipher1Aes256, cipher3Aes128, initialVector1aes256), + Arguments.of("cipher2(aes256)->1(aes256)", cipher2Aes256, cipher1Aes256, initialVector2aes256), + Arguments.of("cipher3(aes128)->1(aes256)", cipher3Aes128, cipher1Aes256, initialVector3aes128), + Arguments.of("cipher3(aes128)->4(aes128)", cipher3Aes128, cipher4Aes128, initialVector3aes128), + Arguments.of("cipher2(aes256)->5(none)", cipher2Aes256, cipher5None, initialVector1aes256), + Arguments.of("cipher5(none)->1(aes256)", cipher5None, cipher1Aes256, initialVector1aes256) + + ); + /* @formatter:on */ + } + } + + private static class DualCipherDifferentInitVectorArgumentsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + /* @formatter:off */ + + return Stream.of( + Arguments.of("cipher1(aes256)->2 (aes256)", cipher1Aes256, cipher2Aes256, initialVector1aes256, initialVector2aes256), + Arguments.of("cipher5(none)->1 (aes256)", cipher5None, cipher2Aes256, initialVector3None, initialVector2aes256), + Arguments.of("cipher2(aes256)->5 (none)", cipher2Aes256, cipher5None, initialVector2aes256, initialVector3None), + Arguments.of("cipher6(none)->4(aes128)", cipher6None, cipher4Aes128, initialVector3None, initialVector2aes256) + + ); + /* @formatter:on */ + } + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/InitializationVectorTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/InitializationVectorTest.java new file mode 100644 index 0000000000..8960ac71ea --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/InitializationVectorTest.java @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class InitializationVectorTest { + + @Test + void null_bytes() throws Exception { + + /* execute */ + InitializationVector initialVectorToTest = new InitializationVector(null); + + /* test */ + assertThat(initialVectorToTest.getInitializationBytes()).isNull(); + + } + + @Test + void filled_bytes() throws Exception { + + /* execute */ + InitializationVector initialVectorToTest = new InitializationVector("filled-bytes...".getBytes()); + + /* test */ + assertThat(initialVectorToTest.getInitializationBytes()).isEqualTo("filled-bytes...".getBytes()); + + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/NoneCipherTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/NoneCipherTest.java new file mode 100644 index 0000000000..d532307447 --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/NoneCipherTest.java @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class NoneCipherTest { + + @Test + void none_cipher_encryption_and_decryption_works_but_encrypted_data_is_origin() { + + /* prepare */ + NoneCipher cipherToTest = new NoneCipher(); + + InitializationVector initVector = cipherToTest.createNewInitializationVector(); + byte[] initVectorInBytes = initVector.getInitializationBytes(); + byte[] dataToEncrypt = "i am the plain text :-)".getBytes(); + + /* execute */ + byte[] encryptedBytes = cipherToTest.encrypt(dataToEncrypt, initVector); + + /* test */ + NoneCipher cipherFromOutside = new NoneCipher(); + byte[] decrypted = cipherFromOutside.decrypt(encryptedBytes, new InitializationVector(initVectorInBytes)); + + assertEquals(new String(dataToEncrypt), new String(decrypted)); + assertEquals(new String(encryptedBytes), new String(dataToEncrypt)); + + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherFactoryTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherFactoryTest.java new file mode 100644 index 0000000000..3e8a611e5a --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/PersistentCipherFactoryTest.java @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PersistentCipherFactoryTest { + + private PersistentCipherFactory factoryToTest; + + @BeforeEach + void beforeEach() { + factoryToTest = new PersistentCipherFactory(); + } + + @Test + void type_aes_gcm_siv_256_can_be_created__with_secret_of_32Byte() { + /* prepare */ + SecretKeyProvider secretKeyProvider = mock(SecretKeyProvider.class); + when(secretKeyProvider.getLengthOfSecretInBits()).thenReturn(256); + + /* execute */ + PersistentCipher cipher = factoryToTest.createCipher(secretKeyProvider, PersistentCipherType.AES_GCM_SIV_256); + + /* test */ + assertTrue(cipher instanceof AesGcmSivCipher); + assertEquals(PersistentCipherType.AES_GCM_SIV_256, cipher.getType()); + + } + + @Test + void type_aes_gcm_siv_1289_can_be_created__with_secret_of128bit() { + /* prepare */ + SecretKeyProvider secretKeyProvider = mock(SecretKeyProvider.class); + when(secretKeyProvider.getLengthOfSecretInBits()).thenReturn(128); + + /* execute */ + PersistentCipher cipher = factoryToTest.createCipher(secretKeyProvider, PersistentCipherType.AES_GCM_SIV_128); + + /* test */ + assertTrue(cipher instanceof AesGcmSivCipher); + assertEquals(PersistentCipherType.AES_GCM_SIV_128, cipher.getType()); + + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 16, 128, 512 }) + void type_aes_gcm_siv_256_can_NOT_be_created__with_secret_having_wrong_amunt_of_bits(int amountOfBits) { + /* prepare */ + SecretKeyProvider secretKeyProvider = mock(SecretKeyProvider.class); + when(secretKeyProvider.getLengthOfSecretInBits()).thenReturn(amountOfBits); + + /* execute */ + assertThrows(IllegalArgumentException.class, () -> factoryToTest.createCipher(secretKeyProvider, PersistentCipherType.AES_GCM_SIV_256)); + + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 16, 64, 256 }) + void type_aes_gcm_siv_128_can_NOT_be_created__with_secret_having_wrong_amunt_of_bits(int amountOfBits) { + + /* prepare */ + SecretKeyProvider secretKeyProvider = mock(SecretKeyProvider.class); + when(secretKeyProvider.getLengthOfSecretInBits()).thenReturn(amountOfBits); + + /* execute */ + assertThrows(IllegalArgumentException.class, () -> factoryToTest.createCipher(secretKeyProvider, PersistentCipherType.AES_GCM_SIV_128)); + + } + + @Test + void type_none_can_be_created() { + + /* execute */ + PersistentCipher cipher = factoryToTest.createCipher(null, PersistentCipherType.NONE); + + /* test */ + assertTrue(cipher instanceof NoneCipher); + assertEquals(PersistentCipherType.NONE, cipher.getType()); + + } + +} diff --git a/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/StringByteArrayTransformerTest.java b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/StringByteArrayTransformerTest.java new file mode 100644 index 0000000000..786a69b899 --- /dev/null +++ b/sechub-commons-encryption/src/test/java/com/mercedesbenz/sechub/commons/encryption/StringByteArrayTransformerTest.java @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.encryption; + +import static org.assertj.core.api.Assertions.*; + +import java.nio.charset.Charset; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class StringByteArrayTransformerTest { + + private StringByteArrayTransformer transformerToTest; + + @BeforeEach + void beforeEach() { + transformerToTest = new StringByteArrayTransformer(); + } + + /* @formatter:off */ + @ParameterizedTest + @NullSource + @EmptySource + @ValueSource(strings= { + "hello world", + "🍐🍌🍓🍉", + "Hello 🦄", + "🥦🥕🥔🫘🥒🫑🌽🍆", + "Äpfel sind grün" + }) + /* @formatter:on */ + void transformFromBytes_bytes_are_transformed_back_via_utf_8(String plainText) { + /* prepare */ + byte[] bytes = plainText == null ? null : plainText.getBytes(Charset.forName("UTF-8")); + + /* execute */ + String result = transformerToTest.transformFromBytes(bytes); + + /* test */ + assertThat(result).isEqualTo(plainText); + } + + /* @formatter:off */ + @ParameterizedTest + @NullSource + @EmptySource + @ValueSource(strings= { + "hello world", + "🍐🍌🍓🍉", + "Hello 🦄", + "🥦🥕🥔🫘🥒🫑🌽🍆", + "Äpfel sind grün" + }) + /* @formatter:on */ + void transformToBytes(String plainText) { + /* prepare */ + + /* execute */ + Object result = transformerToTest.transformToBytes(plainText); + + /* test */ + byte[] expectedBytes = plainText == null ? null : plainText.getBytes(Charset.forName("UTF-8")); + assertThat(result).isEqualTo(expectedBytes); + } + +} diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileReader.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileReader.java index 918c85b969..1b69932759 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileReader.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileReader.java @@ -6,34 +6,49 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.Charset; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A class to read text from files. Will always use UTF-8. + * + * @author Albert Tregnaghi + * + */ public class TextFileReader { + private static final String DEFAULT_LINE_BREAK = "\n"; + private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8"); + + private static final Logger LOG = LoggerFactory.getLogger(TextFileReader.class); + /** - * Load complete text file with default line break (new line) + * Read complete text from file with default line break * * @param file resource to load - * @return string, never {@link NullPointerException} - * @throws IOException + * @return string, never null + * @throws IOException when IO problems occur */ - public String loadTextFile(File file) throws IOException { - return loadTextFile(file, "\n"); + public String readTextFromFile(File file) throws IOException { + return readTextFromFile(file, DEFAULT_LINE_BREAK); } /** - * Load complete text file + * Read complete text from file * * @param file resource to load * @param lineBreak the line break to use - * @return - * @throws IOException + * @return string, never null + * @throws IOException when IO problems occur */ - public String loadTextFile(File file, String lineBreak) throws IOException { - return loadTextFile(file, lineBreak, null); + public String readTextFromFile(File file, String lineBreak) throws IOException { + return readTextFromFile(file, lineBreak, null); } /** - * Loads text file - if max amount of lines is defined, only this amount of + * Read text from file - if max amount of lines is defined, only this amount of * lines will be read. * * @param file resource to load @@ -41,14 +56,16 @@ public String loadTextFile(File file, String lineBreak) throws IOException { * @param maxAmountOfLines maximum amount lines to read - minimum is 1. One line * will always be returned, even when the value is lower * than 1! - * @return - * @throws IOException + * @return string, never null + * @throws IOException when IO problems occur */ - public String loadTextFile(File file, String lineBreak, Integer maxAmountOfLines) throws IOException { + public String readTextFromFile(File file, String lineBreak, Integer maxAmountOfLines) throws IOException { + LOG.trace("Read text from file: {}", file); + StringBuilder sb = new StringBuilder(); int linesRead = 0; - try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { + try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), CHARSET_UTF8))) { String line = null; while ((line = br.readLine()) != null) { diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileWriter.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileWriter.java index 0ec812fc0e..2dd0465a72 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileWriter.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/TextFileWriter.java @@ -12,8 +12,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * A class to write text to files. Will always use UTF-8. + * + * @author Albert Tregnaghi + * + */ public class TextFileWriter { + private static final Charset CHARSET_UTF_8 = Charset.forName("UTF-8"); private static final Logger LOG = LoggerFactory.getLogger(TextFileWriter.class); /** @@ -26,11 +33,7 @@ public class TextFileWriter { * before write * @throws IOException */ - public void save(File targetFile, String text, boolean overwrite) throws IOException { - internalSave(targetFile, text, overwrite, Charset.forName("UTF-8")); - } - - private void internalSave(File targetFile, String text, boolean overwrite, Charset charset) throws IOException { + public void writeTextToFile(File targetFile, String text, boolean overwrite) throws IOException { if (targetFile == null) { throw new IllegalArgumentException("null not allowed as file!"); } @@ -59,7 +62,7 @@ private void internalSave(File targetFile, String text, boolean overwrite, Chars throw new IllegalStateException("was not able to create new file:" + targetFile); } } - try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), charset))) { + try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), CHARSET_UTF_8))) { bw.write(text); } diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/RemoteCredentialContainerTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/RemoteCredentialContainerTest.java index ecc43415d1..6654d5a485 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/RemoteCredentialContainerTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/RemoteCredentialContainerTest.java @@ -20,7 +20,7 @@ class RemoteCredentialContainerTest { @Test void resolve_remote_credentials_by_location_and_accept_all_types() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); RemoteCredentialConfiguration configuration = RemoteCredentialConfiguration.fromJSONString(json); containerToTest = new RemoteCredentialContainerFactory().create(configuration); String location1 = "https://github.com/username/project"; @@ -58,7 +58,7 @@ void resolve_remote_credentials_by_location_and_accept_all_types() { @Test void resolve_remote_credentials_by_location_and_accept_specific_types() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); RemoteCredentialConfiguration configuration = RemoteCredentialConfiguration.fromJSONString(json); containerToTest = new RemoteCredentialContainerFactory().create(configuration); String type1 = "docker"; @@ -158,7 +158,7 @@ void resolve_credentials_from_configuration_where_no_types_are_set_but_all_types @NullSource void resolve_remote_credential_pattern_by_location_with_empty_or_null_location(String location) { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); RemoteCredentialConfiguration configuration = RemoteCredentialConfiguration.fromJSONString(json); containerToTest = new RemoteCredentialContainerFactory().create(configuration); @@ -174,7 +174,7 @@ void resolve_remote_credential_pattern_by_location_with_empty_or_null_location(S @NullSource void resolve_remote_credential_by_unknown_location_with_empty_or_null_type(String type) { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_credentials_config.json")); RemoteCredentialConfiguration configuration = RemoteCredentialConfiguration.fromJSONString(json); containerToTest = new RemoteCredentialContainerFactory().create(configuration); String location = "unknown-location"; diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java index b29db47872..93d9106e7a 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelSupportTest.java @@ -681,7 +681,7 @@ void sechub_secret_scan_config_source_example__source_required_only_by_secret_sc } static SecHubConfigurationModel loadModel(String testFileName) { - String json = TestFileReader.loadTextFile(new File("./src/test/resources/" + testFileName)); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/" + testFileName)); return converter.fromJSON(SecHubConfigurationModel.class, json); } } diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java index 5d832bd13f..0ff3f27301 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubConfigurationModelValidatorTest.java @@ -1370,7 +1370,7 @@ void model_has_multiple_header_values_from_file_ref_and_direct_value_specified_h @ValueSource(strings = { "src/test/resources/sechub_config_web_scan_no_intersection_of_urls_of_same_header.json" }) void explicit_definitions_for_the_same_header_for_certain_urls_but_list_of_urls_have_no_intersections_has_no_errors(String testFilePath) { /* prepare */ - String json = TestFileReader.loadTextFile(testFilePath); + String json = TestFileReader.readTextFromFile(testFilePath); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.WEB_SCAN); @@ -1388,7 +1388,7 @@ void explicit_definitions_for_the_same_header_for_certain_urls_but_list_of_urls_ void explicit_definitions_for_the_same_header_for_certain_urls_but_list_of_urls_have_no_intersections_with_lower_and_upper_cases_has_no_errors( String testFilePath) { /* prepare */ - String json = TestFileReader.loadTextFile(testFilePath); + String json = TestFileReader.readTextFromFile(testFilePath); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.WEB_SCAN); @@ -1404,7 +1404,7 @@ void explicit_definitions_for_the_same_header_for_certain_urls_but_list_of_urls_ @ValueSource(strings = { "src/test/resources/sechub_config_web_scan_default_and_explicit_definitions_for_urls_for_header.json" }) void default_for_a_header_with_explicit_definitions_for_the_same_header_for_certain_urls_has_no_errors(String testFilePath) { /* prepare */ - String json = TestFileReader.loadTextFile(testFilePath); + String json = TestFileReader.readTextFromFile(testFilePath); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.WEB_SCAN); @@ -1421,7 +1421,7 @@ void default_for_a_header_with_explicit_definitions_for_the_same_header_for_cert "src/test/resources/sechub_config_web_scan_duplicated_default_with_upper_case.json" }) void duplicated_default_for_the_same_header_has_error(String testFilePath) { /* prepare */ - String json = TestFileReader.loadTextFile(testFilePath); + String json = TestFileReader.readTextFromFile(testFilePath); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.WEB_SCAN); @@ -1438,7 +1438,7 @@ void duplicated_default_for_the_same_header_has_error(String testFilePath) { "src/test/resources/sechub_config_web_scan_intersection_of_urls_of_same_header_missing_slash.json" }) void explicit_definitions_for_the_same_header_for_certain_urls_but_list_of_urls_do_have_intersections_has_error(String testFilePath) { /* prepare */ - String json = TestFileReader.loadTextFile(testFilePath); + String json = TestFileReader.readTextFromFile(testFilePath); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.WEB_SCAN); @@ -1577,7 +1577,7 @@ void include_too_long_results_in_error() { @Test void can_read_sechub_web_scan_config_with_wildcards() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_config_web_scan_includes_excludes_with_wildcards.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_config_web_scan_includes_excludes_with_wildcards.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.WEB_SCAN); @@ -1619,7 +1619,7 @@ void when_sechub_config_has_exactly_maximum_size_allowed_error_SECHUB_CONFIGURAT @ValueSource(strings = { "src/test/resources/sechub_remote_data_config_binary_code_scan_example.json", "src/test/resources/sechub_remote_data_config_source_code_scan_example.json" }) void when_remote_sechub_configuration_is_valid_no_errors_are_reported(String file) { - String json = TestFileReader.loadTextFile(file); + String json = TestFileReader.readTextFromFile(file); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); @@ -1633,7 +1633,7 @@ void when_remote_sechub_configuration_is_valid_no_errors_are_reported(String fil @Test void when_multiple_remote_configurations_are_configured_error_REMOTE_DATA_CONFIGURATION_ONLY_FOR_ONE_SOURCE_OR_BINARY() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_remote_data_config_invalid_multi_config.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_remote_data_config_invalid_multi_config.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); @@ -1648,7 +1648,7 @@ void when_multiple_remote_configurations_are_configured_error_REMOTE_DATA_CONFIG @Test void when_remote_configuration_location_is_not_defined_error_REMOTE_DATA_CONFIGURATION_LOCATION_NOT_DEFINED() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_remote_data_config_invalid_missing_location.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_remote_data_config_invalid_missing_location.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); @@ -1663,7 +1663,7 @@ void when_remote_configuration_location_is_not_defined_error_REMOTE_DATA_CONFIGU @Test void when_remote_configuration_has_empty_credentials_error_REMOTE_DATA_CONFIGURATION_USER_NOT_DEFINED() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_remote_data_config_invalid_credentials_empty.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_remote_data_config_invalid_credentials_empty.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); @@ -1678,7 +1678,7 @@ void when_remote_configuration_has_empty_credentials_error_REMOTE_DATA_CONFIGURA @Test void when_remote_configuration_credential_user_has_no_username_error_REMOTE_DATA_CONFIGURATION_USER_NAME_NOT_DEFINED() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_remote_data_config_invalid_user_credential_missing_username.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_remote_data_config_invalid_user_credential_missing_username.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); @@ -1693,7 +1693,7 @@ void when_remote_configuration_credential_user_has_no_username_error_REMOTE_DATA @Test void when_remote_configuration_credential_user_has_no_password_error_REMOTE_DATA_CONFIGURATION_USER_PASSWORD_NOT_DEFINED() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_remote_data_config_invalid_user_credential_missing_password.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_remote_data_config_invalid_user_credential_missing_password.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); @@ -1708,7 +1708,7 @@ void when_remote_configuration_credential_user_has_no_password_error_REMOTE_DATA @Test void when_remote_configuration_is_mixed_with_filesystem_REMOTE_REMOTE_DATA_MIXED_WITH_FILESYSTEM_NOT_ALLOWED() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_remote_data_config_invalid_config_with_filesystem.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_remote_data_config_invalid_config_with_filesystem.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); @@ -1723,7 +1723,7 @@ void when_remote_configuration_is_mixed_with_filesystem_REMOTE_REMOTE_DATA_MIXED @Test void when_remote_data_is_configured_for_binaries_and_sources_error() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/sechub_remote_data_config_invalid_source_and_binaries.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub_remote_data_config_invalid_source_and_binaries.json"); SecHubScanConfiguration sechubConfiguration = SecHubScanConfiguration.createFromJSON(json); modelSupportCollectedScanTypes.add(ScanType.CODE_SCAN); diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubReportModelTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubReportModelTest.java index be8759b8b3..f761c31b84 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubReportModelTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubReportModelTest.java @@ -33,7 +33,7 @@ void empty_model_does_not_contain(String notContained) { @Test void sechub_example_result_7_can_be_loaded_and_transformed_from_json() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/report/sechub-testreport7-secret-scan-with-revision-data-and-metadata.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/report/sechub-testreport7-secret-scan-with-revision-data-and-metadata.json"); /* execute 1 - read */ SecHubReportModel model = SecHubReportModel.fromJSONString(json); @@ -60,7 +60,7 @@ void sechub_example_result_7_can_be_loaded_and_transformed_from_json() { @Test void sechub_example_result_6_can_be_loaded_and_transformed_from_json() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/report/sechub-testreport6-secret-scan-with-revision-data.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/report/sechub-testreport6-secret-scan-with-revision-data.json"); /* execute */ SecHubReportModel model = SecHubReportModel.fromJSONString(json); @@ -80,7 +80,7 @@ void sechub_example_result_6_can_be_loaded_and_transformed_from_json() { @Test void sechub_example_result_5_can_be_loaded_and_transformed_from_json() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/report/sechub-testreport5-version-control-data.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/report/sechub-testreport5-version-control-data.json"); /* execute */ SecHubReportModel result = SecHubReportModel.fromJSONString(json); @@ -96,7 +96,7 @@ void sechub_example_result_5_can_be_loaded_and_transformed_from_json() { @Test void sechub_example_result_4_can_be_loaded_and_transformed_from_json() { /* prepare */ - String json = TestFileReader.loadTextFile("src/test/resources/report/sechub-testreport4-multiple-web-findings.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/report/sechub-testreport4-multiple-web-findings.json"); /* execute */ SecHubReportModel result = SecHubReportModel.fromJSONString(json); @@ -108,7 +108,7 @@ void sechub_example_result_4_can_be_loaded_and_transformed_from_json() { @Test void a_report_without_status_and_messages_can_be_read_and_when_traffic_light_is_red_status_is_failed() { /* prepare */ - String jsonNoStatusOrMessages = TestFileReader.loadTextFile(new File("./src/test/resources/report/sechub-testreport3.json"), "\n"); + String jsonNoStatusOrMessages = TestFileReader.readTextFromFile(new File("./src/test/resources/report/sechub-testreport3.json"), "\n"); /* execute */ SecHubReportModel report = SecHubReportModel.fromJSONString(jsonNoStatusOrMessages); @@ -126,7 +126,7 @@ void a_report_without_status_and_messages_can_be_read_and_when_traffic_light_is_ @Test void a_report_without_status_and_messages_can_be_read_and_when_traffic_light_is_green_status_is_ok() { /* prepare */ - String jsonNoStatusOrMessages = TestFileReader.loadTextFile(new File("./src/test/resources/report/sechub-testreport1.json"), "\n"); + String jsonNoStatusOrMessages = TestFileReader.readTextFromFile(new File("./src/test/resources/report/sechub-testreport1.json"), "\n"); /* execute */ SecHubReportModel report = SecHubReportModel.fromJSONString(jsonNoStatusOrMessages); @@ -144,7 +144,7 @@ void a_report_without_status_and_messages_can_be_read_and_when_traffic_light_is_ @Test void a_report_with_status_failed_and_messages_can_be_read() { /* prepare */ - String jsonWithStatusAndMessages = TestFileReader.loadTextFile(new File("./src/test/resources/report/sechub-testreport2.json"), "\n"); + String jsonWithStatusAndMessages = TestFileReader.readTextFromFile(new File("./src/test/resources/report/sechub-testreport2.json"), "\n"); /* execute */ SecHubReportModel report = SecHubReportModel.fromJSONString(jsonWithStatusAndMessages); diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubScanConfigurationTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubScanConfigurationTest.java index bc5dd7c9bd..cb9c78e752 100644 --- a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubScanConfigurationTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubScanConfigurationTest.java @@ -18,7 +18,7 @@ class SecHubScanConfigurationTest { void sechub_job_config_example1_JSON_can_be_deserialized_and_contains_expected_login_url() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_config_example1.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_config_example1.json")); /* execute */ SecHubScanConfiguration scanConfig = SecHubScanConfiguration.createFromJSON(json); @@ -31,7 +31,7 @@ void sechub_job_config_example1_JSON_can_be_deserialized_and_contains_expected_l void sechub_job_config_license_scan_JSON_can_be_deserialized_and_contains_expected_source_data_reference() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_license_scan_config_source_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_license_scan_config_source_example.json")); /* execute */ SecHubScanConfiguration scanConfig = SecHubScanConfiguration.createFromJSON(json); @@ -46,7 +46,7 @@ void sechub_job_config_license_scan_JSON_can_be_deserialized_and_contains_expect void sechub_job_config_license_scan_JSON_can_be_deserialized_even_with_unknown_key() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_license_scan_non_existing_key.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_license_scan_non_existing_key.json")); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -76,7 +76,7 @@ void sechub_job_config_license_scan_JSON_can_be_deserialized_even_with_unknown_k void sechub_job_config_fantasy_scan_type_only_no_official_scan_types_inside_but_can_be_read() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_unknown_scan_type.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_unknown_scan_type.json")); /* execute */ SecHubScanConfiguration scanConfig = SecHubScanConfiguration.createFromJSON(json); @@ -92,7 +92,7 @@ void sechub_job_config_fantasy_scan_type_only_no_official_scan_types_inside_but_ void sechub_job_config_contains_data_section_when_only_fantasy_scan_type_defined() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_unknown_scan_type.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_unknown_scan_type.json")); /* execute */ SecHubScanConfiguration scanConfig = SecHubScanConfiguration.createFromJSON(json); @@ -110,7 +110,7 @@ void sechub_job_config_contains_data_section_when_only_fantasy_scan_type_defined void sechub_job_config_license_scan_JSON_can_be_deserialized_and_contains_expected_binary_data_reference() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_license_scan_config_binary_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_license_scan_config_binary_example.json")); /* execute */ SecHubScanConfiguration scanConfig = SecHubScanConfiguration.createFromJSON(json); @@ -125,7 +125,7 @@ void sechub_job_config_license_scan_JSON_can_be_deserialized_and_contains_expect void sechub_job_config_secret_scan_JSON_can_be_deserialized_and_contains_expected_source_data_reference() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_secret_scan_config_source_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_secret_scan_config_source_example.json")); /* execute */ SecHubScanConfiguration scanConfig = SecHubScanConfiguration.createFromJSON(json); @@ -140,7 +140,7 @@ void sechub_job_config_secret_scan_JSON_can_be_deserialized_and_contains_expecte void sechub_job_config_secret_scan_JSON_can_be_deserialized_and_contains_expected_binary_data_reference() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_secret_scan_config_binary_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_secret_scan_config_binary_example.json")); /* execute */ SecHubScanConfiguration scanConfig = SecHubScanConfiguration.createFromJSON(json); @@ -155,7 +155,7 @@ void sechub_job_config_secret_scan_JSON_can_be_deserialized_and_contains_expecte void sechub_remote_source_code_scan_configuration_contains_location_and_type() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_data_config_source_code_scan_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_data_config_source_code_scan_example.json")); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -199,7 +199,7 @@ void sechub_remote_source_code_scan_configuration_contains_location_and_type() { void sechub_remote_binary_code_scan_configuration_contains_location_and_type() { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_data_config_binary_code_scan_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_data_config_binary_code_scan_example.json")); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); diff --git a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/SecHubTimeUnitTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubTimeUnitTest.java similarity index 97% rename from sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/SecHubTimeUnitTest.java rename to sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubTimeUnitTest.java index 4fbbe43707..5627b027fb 100644 --- a/sechub-adapter/src/test/java/com/mercedesbenz/sechub/adapter/SecHubTimeUnitTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SecHubTimeUnitTest.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.adapter; +package com.mercedesbenz.sechub.commons.model; import static org.junit.jupiter.api.Assertions.*; @@ -11,7 +11,6 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidFormatException; -import com.mercedesbenz.sechub.commons.model.SecHubTimeUnit; public class SecHubTimeUnitTest { @Test diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/SeveritiesTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SeveritiesTest.java similarity index 82% rename from sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/SeveritiesTest.java rename to sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SeveritiesTest.java index 6aac041baf..a228fe55ce 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/SeveritiesTest.java +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/SeveritiesTest.java @@ -1,13 +1,10 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan; +package com.mercedesbenz.sechub.commons.model; import static org.junit.Assert.*; import org.junit.Test; -import com.mercedesbenz.sechub.commons.model.Severities; -import com.mercedesbenz.sechub.commons.model.Severity; - public class SeveritiesTest { @Test diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSUserMessageSupport.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSUserMessageSupport.java index 9b7c20acd2..a68f919b18 100644 --- a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSUserMessageSupport.java +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSUserMessageSupport.java @@ -39,7 +39,7 @@ public void writeMessage(SecHubMessage message) throws IOException { File file = new File(messageFolder, fileName); - writer.save(file, message.getText(), false); + writer.writeTextToFile(file, message.getText(), false); } private String createUniqueFileName(SecHubMessage message) { diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/data/PDSJobStatus.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/data/PDSJobStatus.java index b3fb1d0ae2..a453f13e39 100644 --- a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/data/PDSJobStatus.java +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/data/PDSJobStatus.java @@ -3,23 +3,95 @@ import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * This class represents the schedule job status which can be obtained by REST + * + */ +@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(Include.NON_NULL) public class PDSJobStatus { - public UUID jobUUID; + public static final String PROPERTY_JOBUUID = "jobUUID"; + public static final String PROPERTY_OWNER = "owner"; + public static final String PROPERTY_CREATED = "created"; + public static final String PROPERTY_STARTED = "started"; + public static final String PROPERTY_ENDED = "ended"; + public static final String PROPERTY_STATE = "state"; + public static final String PROPERTY_ENCRYPTION_OUT_OF_SYNCH = "encryptionOutOfSync"; + + private UUID jobUUID; + + private String owner; + + private String created; + private String started; + private String ended; - public String owner; + private boolean encryptionOutOfSync; - public String created; - public String started; - public String ended; + private PDSJobStatusState state; + + public UUID getJobUUID() { + return jobUUID; + } + + public void setJobUUID(UUID jobUUID) { + this.jobUUID = jobUUID; + } - public PDSJobStatusState state; + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getCreated() { + return created; + } + + public void setCreated(String created) { + this.created = created; + } + + public String getStarted() { + return started; + } + + public void setStarted(String started) { + this.started = started; + } + + public String getEnded() { + return ended; + } + + public void setEnded(String ended) { + this.ended = ended; + } + + public PDSJobStatusState getState() { + return state; + } + + public void setState(PDSJobStatusState state) { + this.state = state; + } + + public boolean isEncryptionOutOfSync() { + return encryptionOutOfSync; + } - @Override - public String toString() { - return "PDSJobStatus [" + (state != null ? "state=" + state + ", " : "") + (jobUUID != null ? "jobUUID=" + jobUUID + ", " : "") - + (owner != null ? "owner=" + owner + ", " : "") + (created != null ? "created=" + created + ", " : "") - + (started != null ? "started=" + started + ", " : "") + (ended != null ? "ended=" + ended : "") + "]"; + public void setEncryptionOutOfSync(boolean encryptionOutOfSync) { + this.encryptionOutOfSync = encryptionOutOfSync; } } diff --git a/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSConfigDataKeyProviderTest.java b/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/PDSConfigDataKeyProviderTest.java similarity index 88% rename from sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSConfigDataKeyProviderTest.java rename to sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/PDSConfigDataKeyProviderTest.java index e5f592c23d..e8155a231e 100644 --- a/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSConfigDataKeyProviderTest.java +++ b/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/PDSConfigDataKeyProviderTest.java @@ -1,12 +1,10 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan.product.pds; +package com.mercedesbenz.sechub.commons.pds; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; -import com.mercedesbenz.sechub.commons.pds.PDSConfigDataKeyProvider; - class PDSConfigDataKeyProviderTest { @Test diff --git a/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/ProcessAdapterTest.java b/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/ProcessAdapterTest.java index 32b297818b..3824c079c2 100644 --- a/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/ProcessAdapterTest.java +++ b/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/ProcessAdapterTest.java @@ -39,7 +39,7 @@ void process_adapter_enter_input_works_as_expected() throws Exception { // the bash script has written the input from stdin to a file, now we read and // check the content: - String output = TestFileReader.loadTextFile(tempFile); + String output = TestFileReader.readTextFromFile(tempFile); assertEquals("user-input=my-user input...via stdin", output); } diff --git a/sechub-developertools/build.gradle b/sechub-developertools/build.gradle index e56563b48a..787b032dee 100644 --- a/sechub-developertools/build.gradle +++ b/sechub-developertools/build.gradle @@ -15,6 +15,7 @@ dependencies { implementation project(':sechub-scan') implementation project(':sechub-commons-pds') + implementation project(':sechub-commons-encryption') implementation project(':sechub-wrapper-checkmarx') implementation library.apache_commons_io diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java index 195c06fbcd..e54020c4a8 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java @@ -44,6 +44,8 @@ import com.mercedesbenz.sechub.integrationtest.internal.TestRestHelper; import com.mercedesbenz.sechub.integrationtest.internal.TestRestHelper.RestHelperTarget; import com.mercedesbenz.sechub.sharedkernel.ProductIdentifier; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; import com.mercedesbenz.sechub.sharedkernel.project.ProjectAccessLevel; import com.mercedesbenz.sechub.test.PDSTestURLBuilder; import com.mercedesbenz.sechub.test.SecHubTestURLBuilder; @@ -801,4 +803,12 @@ public String fetchProjectJobInfoForUser(String projectId, int pageSize, int pag return TestJSONHelper.get().createJSON(listPage, true); } + public String rotateEncryption(SecHubEncryptionData data) { + return asTestUser().rotateEncryption(data); + } + + public SecHubEncryptionStatus fetchEncryptionStatus() { + return asTestUser().fetchEncryptionStatus(); + } + } diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/ComboxSelectionDialogUI.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/ComboxSelectionDialogUI.java index 6c78a2baac..949ddce683 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/ComboxSelectionDialogUI.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/ComboxSelectionDialogUI.java @@ -15,10 +15,10 @@ import javax.swing.JPanel; import javax.swing.border.EmptyBorder; -public class ComboxSelectionDialogUI { +public class ComboxSelectionDialogUI { private JLabel label; - private JComboBox combobox; + private JComboBox combobox; private JButton cancelButton; private JButton okButton; @@ -27,7 +27,7 @@ public class ComboxSelectionDialogUI { private JDialog dialog; - public ComboxSelectionDialogUI(JFrame parentFrame, String title, String labelText, List comboboxValues, String initialValue) { + public ComboxSelectionDialogUI(JFrame parentFrame, String title, String labelText, List comboboxValues, T initialValue) { dialog = new JDialog(parentFrame, title, true); JPanel content = new JPanel(new BorderLayout()); @@ -45,12 +45,12 @@ public ComboxSelectionDialogUI(JFrame parentFrame, String title, String labelTex } - private JPanel createComboBoxPanel(String labelText, List values, String initialValue) { + private JPanel createComboBoxPanel(String labelText, List values, T initialValue) { JPanel comboboxPanel = new JPanel(new BorderLayout()); label = new JLabel(labelText); - DefaultComboBoxModel model = new DefaultComboBoxModel<>(); - for (String value : values) { + DefaultComboBoxModel model = new DefaultComboBoxModel<>(); + for (T value : values) { model.addElement(value); } @@ -83,8 +83,9 @@ public void showDialog() { combobox.requestFocusInWindow(); } - public String getSelectionFromCombobox() { - String inputValue = (String) combobox.getSelectedItem(); + public T getSelectionFromCombobox() { + @SuppressWarnings("unchecked") + T inputValue = (T) combobox.getSelectedItem(); return inputValue; } diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java index 37cacc1600..11a85dc139 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java @@ -32,6 +32,10 @@ import com.mercedesbenz.sechub.developertools.admin.ui.action.config.ListExecutionProfilesAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.config.ListExecutorConfigurationsAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.developerbatchops.DeveloperBatchCreateCheckmarxTestSetupAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.encryption.FetchSecHubEncryptionStatusAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.encryption.RotateSecHubEncryptionAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.encryption.SecretKeyGeneratorAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.encryption.TestDecryptToStringAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.integrationtestserver.FetchMockMailsAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.job.CancelJobAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.job.DownloadFullscanDataForJobAction; @@ -206,6 +210,7 @@ private void createMainMenu() { createConfigMenu(); createPDSMenu(); createSecHubClientMenu(); + createEncryptionMenu(); } public void createEditMenu() { @@ -247,6 +252,17 @@ public void createConfigMenu() { menu.add(new ConfigureAutoCleanupAction(context)); } + public void createEncryptionMenu() { + JMenu menu = new JMenu("Encryption"); + menuBar.add(menu); + menu.add(new FetchSecHubEncryptionStatusAction(context)); + menu.addSeparator(); + menu.add(new SecretKeyGeneratorAction(context)); + menu.add(new TestDecryptToStringAction(context)); + menu.addSeparator(); + menu.add(new RotateSecHubEncryptionAction(context)); + } + private ShowProductExecutorTemplatesDialogAction register(ShowProductExecutorTemplatesDialogAction action) { showProductExecutorTemplatesDialogActions.add(action); return action; diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/DialogUI.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/DialogUI.java index 724ee8146e..8d6a7bbf8a 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/DialogUI.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/DialogUI.java @@ -61,7 +61,18 @@ public void warn(String message) { * @return file or null */ public File selectFile(String initialPath) { - return selectFile(initialPath, null); + return selectFile(initialPath, null, null); + } + + /* + * Selects file by file chooser + * + * @param initialPath the initial file path or null + * + * @return file or null + */ + public File selectFile(String initialPath, String title) { + return selectFile(initialPath, null, title); } /** @@ -73,6 +84,11 @@ public File selectFile(String initialPath) { * @return file or null */ public File selectFile(String initialPath, javax.swing.filechooser.FileFilter fileFilter) { + return selectFile(initialPath, fileFilter, null); + } + + public File selectFile(String initialPath, javax.swing.filechooser.FileFilter fileFilter, String title) { + fileChooser.setDialogTitle(title); fileChooser.setFileFilter(fileFilter); if (initialPath != null) { File file = new File(initialPath); @@ -139,8 +155,8 @@ public Optional getUserInput(String message, String defaultValue) { * @param identifier * @return */ - public Optional getUserInputFromCombobox(String message, String title, List comboboxValues, String initialValue) { - ComboxSelectionDialogUI dialog = new ComboxSelectionDialogUI(frame, title, message, comboboxValues, initialValue); + public Optional getUserInputFromCombobox(String message, String title, List comboboxValues, T initialValue) { + ComboxSelectionDialogUI dialog = new ComboxSelectionDialogUI<>(frame, title, message, comboboxValues, initialValue); dialog.showDialog(); if (!dialog.isOkPressed()) { diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/AbstractUIAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/AbstractUIAction.java index 59025f4851..96f46903df 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/AbstractUIAction.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/AbstractUIAction.java @@ -3,6 +3,7 @@ import java.awt.event.ActionEvent; import java.net.URL; +import java.util.Arrays; import java.util.Optional; import javax.swing.AbstractAction; @@ -214,6 +215,17 @@ protected Optional getUserPassword(String message, InputCacheIdentifier return x; } + /** + * Shows an input dialog for selecting one of given types + * + * @param title + * @param text + * @return + */ + protected Optional getUserInputFromCombobox(String title, T initialValue, String message, @SuppressWarnings({ "unchecked" }) T... identifier) { + return getContext().getDialogUI().getUserInputFromCombobox(message, title, Arrays.asList(identifier), initialValue); + } + /** * Shows an input dialog for user (multi line) and sets given text as content * diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/FetchSecHubEncryptionStatusAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/FetchSecHubEncryptionStatusAction.java new file mode 100644 index 0000000000..4e4c3b6393 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/FetchSecHubEncryptionStatusAction.java @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.encryption; + +import java.awt.event.ActionEvent; + +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; + +public class FetchSecHubEncryptionStatusAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + public FetchSecHubEncryptionStatusAction(UIContext context) { + super("Fetch SecHub encryption status", context); + } + + @Override + public void execute(ActionEvent e) { + + SecHubEncryptionStatus status = getContext().getAdministration().fetchEncryptionStatus(); + outputAsTextOnSuccess(status.toFormattedJSON()); + } + +} \ No newline at end of file diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/RotateSecHubEncryptionAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/RotateSecHubEncryptionAction.java new file mode 100644 index 0000000000..0843b17d61 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/RotateSecHubEncryptionAction.java @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.encryption; + +import java.awt.event.ActionEvent; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; + +public class RotateSecHubEncryptionAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + private static final Logger LOG = LoggerFactory.getLogger(RotateSecHubEncryptionAction.class); + + public RotateSecHubEncryptionAction(UIContext context) { + super("Rotate SecHub encryption", context); + } + + @Override + public void execute(ActionEvent e) { + + Optional optSelectedAlgorithm = getUserInputFromCombobox("Select algorithm to use for encryption", + SecHubCipherAlgorithm.AES_GCM_SIV_256, "Select algorithm", SecHubCipherAlgorithm.values()); + if (!optSelectedAlgorithm.isPresent()) { + return; + } + String sourceData = null; + SecHubCipherPasswordSourceType sourceType; + SecHubCipherAlgorithm selectedAlgorithm = optSelectedAlgorithm.get(); + switch (selectedAlgorithm) { + case NONE: + sourceType = SecHubCipherPasswordSourceType.NONE; + break; + case AES_GCM_SIV_128: + case AES_GCM_SIV_256: + default: + sourceType = SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE; + Optional optEnvironmentVariableName = getUserInput("Please enter environment name for password"); + if (!optEnvironmentVariableName.isPresent()) { + return; + } + sourceData = optEnvironmentVariableName.get(); + break; + + } + String confirmMessage = """ + You want to start encryption rotation with: + + Cipher algorithm : '%s' + Password source + - type: '%s' + - data: '%s' + + This will update encryption pool on all SecHub servers inside cluster + and will also start automated re-encryption of jobs which are in state + CANCELED or ENDED. + + Are you sure ? + """.formatted(selectedAlgorithm, sourceType, sourceData); + if (!confirm(confirmMessage)) { + return; + } + LOG.info("start encryption rotation with algorithm: {}, password souce type: {}, password source data: {}", selectedAlgorithm, sourceType, sourceData); + + SecHubEncryptionData data = new SecHubEncryptionData(); + data.setAlgorithm(selectedAlgorithm); + data.setPasswordSourceType(sourceType); + data.setPasswordSourceData(sourceData); + + String infoMessage = getContext().getAdministration().rotateEncryption(data); + outputAsTextOnSuccess(infoMessage); + } + + @Override + protected boolean canConfirmationBeOverridenBySetup() { + return false; + } + +} \ No newline at end of file diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/SecretKeyGeneratorAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/SecretKeyGeneratorAction.java new file mode 100644 index 0000000000..23ecd26841 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/SecretKeyGeneratorAction.java @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.encryption; + +import java.awt.event.ActionEvent; +import java.util.Base64; +import java.util.Base64.Encoder; +import java.util.random.RandomGenerator; +import java.util.random.RandomGeneratorFactory; + +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; + +public class SecretKeyGeneratorAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + private RandomGenerator randomGenerator; + + public SecretKeyGeneratorAction(UIContext context) { + super("Secret key generator", context); + randomGenerator = RandomGeneratorFactory.of("SecureRandom").create(); + } + + @Override + public void execute(ActionEvent e) { + + byte[] random256Bit = new byte[32]; + byte[] random128Bit = new byte[16]; + + randomGenerator.nextBytes(random256Bit); + randomGenerator.nextBytes(random128Bit); + + Encoder encoder = Base64.getEncoder(); + String random256BitBase64encoded = encoder.encodeToString(random256Bit); + String random128BitBase64encoded = encoder.encodeToString(random128Bit); + + String output = """ + Generated random keys + (base64 encoded - ready to use as environment variables for encryption ) + + 256 bit: %s + usable for + - %s + + 128 bit: %s + usable for + - %s + + + """.formatted(random256BitBase64encoded, PersistentCipherType.AES_GCM_SIV_256, random128BitBase64encoded, PersistentCipherType.AES_GCM_SIV_128); + + outputAsTextOnSuccess(output); + } + +} diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/TestDecryptToStringAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/TestDecryptToStringAction.java new file mode 100644 index 0000000000..fbd26d6798 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/encryption/TestDecryptToStringAction.java @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.encryption; + +import java.awt.event.ActionEvent; +import java.io.File; +import java.nio.file.Files; +import java.util.Base64; +import java.util.Optional; + +import com.mercedesbenz.sechub.commons.encryption.DefaultSecretKeyProvider; +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; + +public class TestDecryptToStringAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + private static final PersistentCipherFactory cipherFactory = new PersistentCipherFactory(); + private static final EncryptionSupport encryptionSupport = new EncryptionSupport(); + + public TestDecryptToStringAction(UIContext context) { + super("Test decrypt to string", context); + } + + @Override + public void execute(ActionEvent e) { + + /* -------algorithm--------------------------- */ + Optional optSelectedAlgorithm = getUserInputFromCombobox("Select algorithm to use for encryption", + SecHubCipherAlgorithm.AES_GCM_SIV_256, "Select algorithm", SecHubCipherAlgorithm.values()); + if (!optSelectedAlgorithm.isPresent()) { + return; + } + SecHubCipherAlgorithm selectedAlgorithm = optSelectedAlgorithm.get(); + + /* ------secret key---------------------------- */ + Optional optBase64testSecretKey = getUserPassword("Base64 test secret key", null); + if (optBase64testSecretKey.isEmpty()) { + return; + } + String base64 = optBase64testSecretKey.get(); + byte[] decoded = Base64.getDecoder().decode(base64.getBytes()); + SecretKeyProvider secretKeyProvider = null; + + switch (selectedAlgorithm) { + case NONE: + secretKeyProvider = null; + break; + default: + secretKeyProvider = new DefaultSecretKeyProvider(decoded, selectedAlgorithm.getType()); + } + + output("secret key provider:" + secretKeyProvider); + + /* ------create and use cipher---------------------- */ + PersistentCipher cipher = cipherFactory.createCipher(secretKeyProvider, selectedAlgorithm.getType()); + + /* ------select files------------------------------- */ + String userHomeFolder = System.getProperty("user.home"); + File encryptedDataFile = getContext().getDialogUI().selectFile(userHomeFolder, "Please select encrypted data file"); + if (encryptedDataFile == null) { + return; + } + File initialVectorFile = getContext().getDialogUI().selectFile(userHomeFolder, "Please select initial vector file"); + if (initialVectorFile == null) { + return; + } + + /* ------decrypt------------------------------- */ + try { + byte[] dataBytes = Files.readAllBytes(encryptedDataFile.toPath()); + byte[] vectorBytes = Files.readAllBytes(initialVectorFile.toPath()); + + String decryptedAsString = encryptionSupport.decryptString(dataBytes, cipher, new InitializationVector(vectorBytes)); + File file = new File(encryptedDataFile.getParentFile(), encryptedDataFile.getName() + ".txt"); + Files.writeString(file.toPath(), decryptedAsString); + + output("Decrypted file written to " + file); + + } catch (Exception ex) { + error(ex.getMessage()); + ex.printStackTrace(); + } + + } + +} diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/status/ListStatusEntriesAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/status/ListStatusEntriesAction.java index 85e91563bb..42c9fe5eb9 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/status/ListStatusEntriesAction.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/status/ListStatusEntriesAction.java @@ -42,7 +42,7 @@ public Map getLastDataAsKeyValueMap() { try { root = h.getMapper().readTree(json); } catch (JsonProcessingException e) { - throw new RuntimeException("json not readable", e); + throw new IllegalStateException("json not readable", e); } ArrayNode array = (ArrayNode) root; for (JsonNode node : array) { diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_pds.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_pds.puml new file mode 100644 index 0000000000..b8885341cb --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_pds.puml @@ -0,0 +1,44 @@ +' SPDX-License-Identifier: MIT +@startuml + +'Hide empty parts: +hide empty fields +hide empty methods + +'You can find more examles at https://plantuml.com/class-diagram + +skinparam linetype ortho +'skinparam linetype polyline +package com.mercedesbenz.sechub.commons.encryption as common_encrypt { + + class PersistentCipherFactory + class PersistentCipher + class EncryptionSupport +} + +package com.mercedesbenz.sechub.pds as pds_root{ + + class PDSStartupAssertEnvironmentVariablesUsed { + } + + package encryption as pds_encryption{ + class PDSEncryptionConfiguration #aliceblue ##darkblue + class PDSEncryptionService #aliceblue ##darkblue { + + } + } + package job as pds_job{ + class PDSCreateJobService + class PDSJobConfigurationAccess + } +} +PDSEncryptionService ..> PDSEncryptionConfiguration +PersistentCipherFactory --> PersistentCipher + +PDSEncryptionService ..> PersistentCipherFactory +PDSEncryptionService ..> EncryptionSupport +PDSEncryptionService --> PersistentCipher + + +PDSJobConfigurationAccess --> PDSEncryptionService +PDSCreateJobService --> PDSEncryptionService \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_sechub_config.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_sechub_config.puml new file mode 100644 index 0000000000..3e8cce1cd7 --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_sechub_config.puml @@ -0,0 +1,96 @@ +' SPDX-License-Identifier: MIT +@startuml + +'Hide empty parts: +hide empty fields +hide empty methods + +'You can find more examles at https://plantuml.com/class-diagram + +'skinparam linetype ortho +'skinparam linetype polyline + +database START_ENCRYPTION_ROTATION as rotateEvent #darkorange { +} +note right of rotateEvent +asynchronous +end note +database SCHEDULE_ENCRYPTION_POOL_INITIALIZED as poolInitEvent #limegreen { +} +note right of poolInitEvent +asynchronous +end note + +package com.mercedesbenz.sechub.domain.administration { + package encryption as adm_encryption{ + class EncryptionAdministrationRestController + class AdministrationEncryptionRotationService + } +} + +package com.mercedesbenz.sechub.domain.schedule{ + + package job as schedule_job { + class ScheduleSecHubJob { + byte[] getEncryptedConfiguration() + } + + class SecHubJobFactory { + + } + + class SecHubConfigurationModelAccess { + resolveUnencryptedConfiguration(ScheduleSecHubJob job) + } + + class ScheduleSecHubJobEncryptionUpdateService #limegreen ##green { + updateEncryptedDataIfNecessary() + } + + } + + package encryption as schedule_encryption { + + class ScheduleRefreshEncryptionServiceSetupTriggerService #aliceblue ##darkblue { + triggerEncryptionSetupRefresh() + } + + class ScheduleEncryptionService #aliceblue ##darkblue { + applicationStarted() + refreshEncryptionPoolAndLatestIdIfNecessary() + + encryptWithLatestCipher(String plainText) + String decryptToString(byte[] encrypted, Long encryptionPoolId, InitializationVector initialVector) + ScheduleEncryptionResult rotateEncryption(byte[] data, Long oldCipherPoolId, InitializationVector oldInitialVector) + } + + class ScheduleCipherPoolData #darkorange { + } + + class ScheduleEncryptionPool #aliceblue ##darkblue { + PersistentCipher getCipherForPoolId(Long poolId) + } + + class ScheduleEncryptionRotationService #darkorange { + startEncryptionRotation() + } + } + +EncryptionAdministrationRestController -> AdministrationEncryptionRotationService +AdministrationEncryptionRotationService --> rotateEvent +rotateEvent -[#darkorange]-> ScheduleEncryptionRotationService + +ScheduleEncryptionRotationService -[#darkorange]-> ScheduleCipherPoolData : (A1) create new + +ScheduleRefreshEncryptionServiceSetupTriggerService -[#darkblue]-> ScheduleEncryptionService: (B1) trigger refresh +ScheduleEncryptionRotationService -[#darkorange]-> ScheduleEncryptionService: (A2) trigger refresh +ScheduleEncryptionPool <-[#darkblue]- ScheduleEncryptionService: (A3,B2): refesh pool + +ScheduleEncryptionService -[#darkblue]-> poolInitEvent: (A4,B3): trigger update +poolInitEvent -[#limegreen]-> ScheduleSecHubJobEncryptionUpdateService: (A5,B4): trigger update + +SecHubConfigurationModelAccess --> ScheduleSecHubJob +SecHubJobFactory ..> ScheduleSecHubJob: create +ScheduleSecHubJobEncryptionUpdateService -[#limegreen]-> ScheduleSecHubJob: updates + +@enduml diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_sechub_use_of_commons.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_sechub_use_of_commons.puml new file mode 100644 index 0000000000..e49711130c --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_encryption_sechub_use_of_commons.puml @@ -0,0 +1,31 @@ +' SPDX-License-Identifier: MIT +@startuml + +'Hide empty parts: +hide empty fields +hide empty methods + +'You can find more examles at https://plantuml.com/class-diagram + +skinparam linetype ortho +'skinparam linetype polyline +package com.mercedesbenz.sechub.commons.encryption as common_encrypt { + + class PersistentCipherFactory + class PersistentCipher + class EncryptionSupport +} + +package com.mercedesbenz.sechub.domain.schedule.encryption as schedule_encrypt{ + class ScheduleEncryptionService #aliceblue ##darkblue + class ScheduleEncryptionPool #aliceblue ##darkblue { + PersistentCipher getCipherForPoolId(Long poolId) + } +} + +PersistentCipherFactory --> PersistentCipher + +ScheduleEncryptionService ..> PersistentCipherFactory +ScheduleEncryptionService -[#darkblue]> ScheduleEncryptionPool +ScheduleEncryptionService ..> EncryptionSupport +ScheduleEncryptionPool --> PersistentCipher \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_pds_events_storage.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_pds_events_storage.puml index 8e77a84faa..4b02555452 100644 --- a/sechub-doc/src/docs/asciidoc/diagrams/diagram_pds_events_storage.puml +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_pds_events_storage.puml @@ -72,7 +72,7 @@ note bottom of eventFile Remark: Currently not implemented, but if an event type shall supports multiple - files in fture the name pattern shall be: + files in future the name pattern shall be: "${eventTypeName}[${nr}].json" end note diff --git a/sechub-doc/src/docs/asciidoc/documents/architecture/08_concepts.adoc b/sechub-doc/src/docs/asciidoc/documents/architecture/08_concepts.adoc index 7dbf99a8ff..ee26c07351 100644 --- a/sechub-doc/src/docs/asciidoc/documents/architecture/08_concepts.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/architecture/08_concepts.adoc @@ -43,4 +43,7 @@ include::../shared/concepts/concept_analytic.adoc[] === Statistics include::../shared/concepts/concept_statistic.adoc[] +=== Data encryption +include::../shared/concepts/concept_sechub_data_encryption.adoc[] + diff --git a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/upload_binaries_description.adoc b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/upload_binaries_description.adoc index 20848289c4..15121b7b03 100644 --- a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/upload_binaries_description.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/upload_binaries_description.adoc @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT [[sechub-doclink-uc-user-uploads-binaries-for-job]] -A user wants to upload binearies for a former <>. +A user wants to upload binaries for a former <>. The binaries must be inside a valid tar file. \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_auto_clean.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_auto_clean.adoc index 4613ec8959..ebab254553 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_auto_clean.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_auto_clean.adoc @@ -3,6 +3,8 @@ === Auto cleanup To prevent full hard drives there is an option to automatically remove old data. +It also cleans up old encryption settings when it comes to <>. + [NOTE] ==== See also diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_pds.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_pds.adoc index 7b27ec39ce..d4cb3ddfe1 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_pds.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_pds.adoc @@ -18,7 +18,7 @@ need to provide a `ProductDelegationServer` (in short form `PDS`). - a spring boot application which uses a network DB for providing cluster possibility - a complete standalone application without runtime dependencies to {sechub} server (or its shared kernel) - provides REST access -- a very simple priviledge model with just two users (`tech user` + `admin user`), +- a very simple privilege model with just two users (`tech user` + `admin user`), basic auth via `TLS`, credentials are simply defined by environment entries on startup - provides jobs, queing, monitoring etc. - can execute single files (e.g. a bash script), where job parameters are @@ -32,6 +32,16 @@ need to provide a `ProductDelegationServer` (in short form `PDS`). ==== Big picture plantuml::diagrams/diagram_concept_product_delgation_server_bigpicture.puml[] +==== Encryption +include::concept_pds_data_encryption.adoc[] + +[[concept-pds-auto-cleanup]] +==== Auto cleanup +The PDS provides an auto cleanup mechanism which will remove old PDS jobs automatically. + +The default configuration is set to 2 days. +Administrators can change the default configuration via REST. + [[pds-storage-and-sharing]] ==== Storage and sharing @@ -211,7 +221,7 @@ The Adapter will always be the same, but filled with other necessary parameters. NOTE: So there will be no need to write any adapter or executor when using PDS! -=== HowTo integrate a new product via PDS +==== HowTo integrate a new product via PDS Having new security product XYZ but being a command line tool, we @@ -247,6 +257,13 @@ Having new security product XYZ but being a command line tool, we - test via {sechub} client by starting a new {sechub} job which shall use the product and verify results +[CAUTION] +==== +Output and error stream of a PDS launcher script are stored in {pds} database as plain text! +Means: NEVER log any sensitive data in launcher scripts! + +If you want to give hints for debugging etc. you have to mask the information in log output. +==== diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_pds_data_encryption.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_pds_data_encryption.adoc new file mode 100644 index 0000000000..7187325098 --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_pds_data_encryption.adoc @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +[[section-shared-concepts-pds-data-encryption]] +In {pds} we can have also some sensitive data we want to be encrypted. For example: The remote data +section inside the sechub job configuration contains credentials to fetch data. +Such sensitive information shall be always encrypted. + +===== General +We want + +. Simple encryption rotation approach + +In contrast to {sechub}, the data in the {pds} is only temporary and is not made available for a +longer period of time. Subsequent access to encrypted data is also no longer necessary, +but only while a SecHub job is running. + + + + +This means we simply accept the situation that a PDS restart with new encryption setup could +lead to a situation where a former created job is no longer executable by PDS. + +When the encryption changes for a job between its creation and when it begins running, the job will +be marked automatically as failed and having encryption out of sync. +The PDS product executor at {sechub} side will take care of such a situation and will restart +a new PDS job (which will then be encrypted correctly again). + +. Full automated + +There is no need for manual interaction - means it is not necessary to create any cron jobs or +something else to convert non encrypted data to encrypted data or to rotate a password or to +use a new encryption method. + +. Data protection /Privacy policy +- Even for administrators it shall not be possible to fetch the information directly + + _(of course a person who knows the encryption password and has access to the database will always + be able to calculate values - but we separate here between administration and operation inside + this concept, so protection is fully possible)_ +- The data must not be accidentally made available in decrypted form - for example through a REST + call in which the data object is passed along unencrypted. + +. Easy encryption administration + - It shall be possible for an administrator to configure a new cipher entry at deployment time + +. Secure storage of encryption passwords + + - Encryption passwords are always provided via environment entries, we store always + the environment variable name to use inside the database but never plain values! + +===== PDS startup +A {pds} server only knows the encryption defined inside two variables: + +- `PDS_ENCRYPTION_SECRET_KEY` + + contains the base64 encoded secret key used for encryption +- `PDS_ENCRYPTION_ALGORITHM` + + contains the information about the used encryption algorithm. Can be + + `NONE`, `AES_GCM_SIV_128` or `AES_GCM_SIV_256` . + + +This setup will be used inside the complete instance as long as it is running. +There is no pooling of different encryptions (in constrast to {sechub}, where pooling feature exists). + +[IMPORTANT] +==== +If the secret key is not a base 64 value the server startup will fail! +==== + + +===== Administration +[[section-shared-concepts-pds-data-encryption-rotation]] +====== Encryption rotation +There is no complete rotation of encryption - old data will have no encryption update. + +But an administrator is able to do re-deployment of the PDS cluster +and using other secret or algorithm. + +This will + +- use new encryption setup for all new PDS jobs +- keep existing encrypted data as is +- can lead to a rare race condition when {sechub} has created the job with old PDS instance and + new PDS instance tries to run the PDS job (the access to the encrypted data is no longer possible) + +[TIP] +==== +Via <> the old data will automatically disappear. +If an encryption cleanup for PDS via auto cleanup is too late (e.g. credentials were leaked and +an update is really urgent) , it is still possible to just delete +via SQL all jobs at database which have a timestamp older then the newest deployment time (or +just all). +==== + +====== Encryption status +There is no direct possibility to check encryption status. But the job contains a creation time stamp +and can be mapped to the startup of containers if this would become necessary. + +====== Cleanup old encrypted data +<> automatically removes old information. +This means that old encrypted information (with older encryption settings) automatically +disappears after a certain period of time. + +Since no other encryption data is persisted except in the PDS job, nothing else needs to be cleaned up. + +===== Diagrams +plantuml::diagrams/diagram_encryption_pds.puml[format=svg, title="title"] + diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_sechub_data_encryption.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_sechub_data_encryption.adoc new file mode 100644 index 0000000000..c34ee3a8d1 --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_sechub_data_encryption.adoc @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT +[[section-shared-concepts-sechub-data-encryption]] +In {sechub} we have some sensitive data we want to be encrypted. For example: Inside remote data +section the configuration contains credentials to fetch data. These sensitive information shall +be always encrypted. + +==== General +We want + +. Data consistency + +- It shall not be possible that we loose data by encryption in any way. + + It must be ensured that the servers are always able to read and write data. + +. Full automated cipher rotation + +There is no need for manual interaction - means it is not necessary to create any cron jobs or +something else to convert non encrypted data to encrypted data or to rotate a password or to +use a new encryption method. + +. Data protection /Privacy policy +- Even for administrators it shall not be possible to fetch the information directly + + _(of course a person who knows the encryption password and has access to the database will always + be able to calculate values - but we separate here between administration and operation inside + this concept, so protection is fully possible)_ +- The data must not be accidentally made available in decrypted form - for example through a REST + call in which the data object is passed along unencrypted. + +. Easy encryption administration + - It shall be possible for an administrator to configure a new cipher entry via REST + +. Secure storage of encryption passwords + + - Encryption passwords are always provided via environment entries, we store always + the environment variable name to use inside the database but never plain values! + +==== Server startup +A {sechub} server will stop on startup phase when one of the entries inside the cipher pool cannot +be handled by this server. + +This ensures that every new started server is able to handle all of them / is always readable. + +==== Administration +[[section-shared-concepts-sechub-data-encryption-rotation]] +===== Encryption rotation + +An administrator is able to start encryption rotation via REST. This will + +- use new encryption setup for all new data +- automatically convert existing encrypted data with new encryption setup in background + +===== Encryption status +An administrator is able to fetch encryption status from {sechub} server. All domains which are +doing data encryption add their current status information into result. + +===== Cleanup old encryption setup +<> automatically removes old information. +This means that old encrypted information that cannot be updated for some reason may eventually +disappear and old encryption configurations are then no longer needed and can be removed. + +To fully automate this, after the respective Auto Cleanup, the domains are always checked for encryption configurations that are no longer used and these are then automatically removed (except for the most recent encryption configuration). + + +[IMPORTANT] +==== +If you have setup auto cleanup to 0 days, the auto cleanup is disabled completely and +unused encryption setup will also not be removed. +==== + +==== Scheduler +Inside the schedule domain, the sensitive information is the sechub job configuration. + +===== Database +====== Table +We store the cipher information inside table: `SCHEDULE_CIPHER_POOL_DATA`. + +[NOTE] +==== +Why in schedule domain and only there? Because it is the responsible domain for the storage. All other +domains may NEVER persist this information (for `PDS` the configuration will be sent from SecHub +and stored at `PDS` side encrypted as well) +==== + +Here an an overview of the table (names can be different in database): + +[options="header"] +|=== +|id |algorithm | password_source_type |password_source_data| encoding |test_text | test_initial_vector| test_encrypted | creation_timestamp |created_from +//----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +|0 |NO_ENCRYPTION | PLAIN_TEXT | | PLAIN |no-encryption | | no-encryption | 2024-06-24_10:00:01 | null +|1 |AES_GCM_SIV_128| ENVIRONMENT_VARIABLE |SECHUB_CRYPTO_P0 | PLAIN |SecHub | easdfa313334 | 53d$125666eeffeded | 2024-06-24_10:05:34 | Admin1 +|2 |AES_GCM_SIV_256| ENVIRONMENT_VARIABLE |SECHUB_CRYPTO_P1 | PLAIN |Apfel | fxadsfeec33s | 13x313412124$rfewd | 2024-06-24_11:02:14 | Admin2 +|=== + + +*algorithm* + +Algorithm to use in encryption - currently we provide: + +- NONE (means not encrypted!) +- AES_GCM_SIV_128 +- AES_GCM_SIV_256 + +*password_source_type* + +Currently supported password source types are + +. ENVIRONMENT_VARIABLE + + Here we provide environment variables, the password source data is the name of the environment variable +. NONE + + No password - only allowed for `NONE` algorithm + + +We separated source type and source data to be able to provide additional source - e.g. a password fault for the future. + +*password_source_data* + +Depends on the source + +- If source is `env` than this is the name of the environment variable which holds the secret + +====== Usage inside rows + +Inside the encrypted rows we will persist the *pool id* together with an *initial vector* + +*initial vector* +Some algorithm like `AES_GCM_SIV` do need an initial vector to encrypt secure. The value here is +auto generated by SecHub and is dependent on the algorithm. + +SecHub will always auto generate a dedicate value when it comes to encryption and the vector +will be stored together with the encrypted data. If the initial vector is changed, the row cannot +be decrypted, even when the secret key is known! + +===== Constraints on scheduling +The only situation we need to access the encrypted job configuration is the point, when +it comes to job execution. At all other situations it does not matter if the configuration +can be decrypted or not. + +This means that it may not be possible that an scheduler instance executes a job which is +not supported by the current encryption pool! + +==== Handling server updates +===== {sechub} server 1.x to 2.x +Old server versions do not have the encryption field inside the scheduler job table or the cipher pool table. + +Our SQL migration scripts will initialize scheduler cipher pool table on creation time with a +`NONE` entry (pool id = 0). This is encryption setup (meaning no encryption) will be added +to all existing jobs. + +We want to have zero downtime and rolling updates with k8s and SecHub. To provide this, +it must be ensured, that there is no old server running which creates new jobs with +plain text configurations while update is running. To prevent such a situation +the column name inside `schedule_sechub_job` have been renamed from `configuration` to `unencrypted_configuration`. +If there appears any race conditions, old servers would no longer be able to write data and a +SQL error would happen. + +==== Handling server downgrade +===== {sechub} server 2.x to 1.x +For a downgrade from {sechub} server V2.x to V1.x it is necessary to ensure, that all data is +encrypted with `NONE` cipher type (can be done by encryption rotation). When ensured that everything +is "encrypted" with this cipher type, the old server version can be deployed/used and migration +is automatically done as usual. + +==== Handling sensitive data at runtime +JVM crash dumps contain string information. Classes containing sensitive information shall +store such information inside sealed objects. + +==== Handling metadata from job configuration +The {secHub} configuration is encrypted, because it can contain sensitive data. E.g. when defining a remote data +section. + +There exists a REST endpoint which gives users the possiblity to fetch job information, together with +the meta data defined inside the {sechub} configuration. + +To obtain this information, the configuration will be decrypted temporary at runtime and the meta +data are resolved and returned. + +Because meta data shall not contain any sensitive information, this will not be audit logged. + + +==== Diagrams +===== Usage of encryption commons +plantuml::diagrams/diagram_encryption_sechub_use_of_commons.puml[] + +===== Encryption rotation overview +plantuml::diagrams/diagram_encryption_sechub_config.puml[title='a reduced view of the steps done on encryption rotation'] diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/configuration/eclipse-java-sechub-formatter-setup.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/configuration/eclipse-java-sechub-formatter-setup.adoc new file mode 100644 index 0000000000..51555b734c --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/configuration/eclipse-java-sechub-formatter-setup.adoc @@ -0,0 +1,15 @@ +. Download the formatter xml file https://github.com/Daimler/sechub/files/4158667/sechub-eclipse-sourceformatter-setup.zip[here] and unzip it. + +. Open Java Formatter page in preferences (Window->Preferences). Then press the import button and select the former unpacked xml file. ++ +image::eclipse-java-formatter-import.png[] + +. Ensure SecHub is your active profile. Select `SecHub` as active profile, then press apply and close. ++ +image::eclipse-java-formatter-activate.png[] + +. Before pushing your code please check your java format by executing spotless check. ++ +---- +./gradlew clean spotlessCheck +---- \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/configuration/intelliJ-java-sechub-formatter-setup.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/configuration/intelliJ-java-sechub-formatter-setup.adoc new file mode 100644 index 0000000000..8ce6faba06 --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/configuration/intelliJ-java-sechub-formatter-setup.adoc @@ -0,0 +1,15 @@ +. Download the formatter xml file https://github.com/Daimler/sechub/files/4158667/sechub-eclipse-sourceformatter-setup.zip[here] and unzip it. + +. Open Java Formatter page in preferences (File->Settings). Then press the import button and select the former unpacked xml file. ++ +image::intelliJ-java-formatter-import.png[] + +. Ensure SecHub is your active profile. Select `SecHub` as active profile, then press apply and close. ++ +image::intelliJ-java-formatter-activate.png[] + +. Before pushing your code please check your java format by executing spotless check. ++ +---- +./gradlew clean spotlessCheck +---- \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/configuration/sechub_config.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/configuration/sechub_config.adoc index 4ee7891ef4..f6a3c376d7 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/configuration/sechub_config.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/shared/configuration/sechub_config.adoc @@ -480,6 +480,15 @@ include::sechub_config_example20_remote_data_with_credentials_binaries_licensesc ==== MetaData The {sechub} configuration file can have optional meta data. +[IMPORTANT] +==== +The {sechub} configuration is stored encrypted in database and access is restricted, even +for administrators. But the meta data can be fetched by users of the project or administrators +without additional audit logging. + +Because of this you should never store sensitive information inside the meta data! +==== + ===== Labels With labels a user is able to add additional information to the scan configuration which end up in the report as key value pairs. diff --git a/sechub-doc/src/docs/asciidoc/documents/techdoc/01_development.adoc b/sechub-doc/src/docs/asciidoc/documents/techdoc/01_development.adoc index ea6cbb27fc..50a7174622 100644 --- a/sechub-doc/src/docs/asciidoc/documents/techdoc/01_development.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/techdoc/01_development.adoc @@ -57,6 +57,10 @@ of your choice. ====== Create localhost server certificate This is automatically done by former gradle call, so not necessary here. +====== Add Sechub Java Code Formatter + +include::../shared/configuration/eclipse-java-sechub-formatter-setup.adoc[] + ===== Others ====== Import projects Import as you would normally do in your IDE. @@ -67,6 +71,9 @@ just call `./gradlew ensureLocalhostCertificate` This will generate a self signed server certificate for localhost. +====== Add Sechub Java Code Formatter (IntelliJ) + +include::../shared/configuration/intelliJ-java-sechub-formatter-setup.adoc[] ==== Special developer files diff --git a/sechub-doc/src/docs/asciidoc/images/eclipse-java-formatter-activate.png b/sechub-doc/src/docs/asciidoc/images/eclipse-java-formatter-activate.png new file mode 100644 index 0000000000..0cd3f69c4e Binary files /dev/null and b/sechub-doc/src/docs/asciidoc/images/eclipse-java-formatter-activate.png differ diff --git a/sechub-doc/src/docs/asciidoc/images/eclipse-java-formatter-import.png b/sechub-doc/src/docs/asciidoc/images/eclipse-java-formatter-import.png new file mode 100644 index 0000000000..cf216a12d3 Binary files /dev/null and b/sechub-doc/src/docs/asciidoc/images/eclipse-java-formatter-import.png differ diff --git a/sechub-doc/src/docs/asciidoc/images/intelliJ-java-formatter-activate.png b/sechub-doc/src/docs/asciidoc/images/intelliJ-java-formatter-activate.png new file mode 100644 index 0000000000..822c0fd643 Binary files /dev/null and b/sechub-doc/src/docs/asciidoc/images/intelliJ-java-formatter-activate.png differ diff --git a/sechub-doc/src/docs/asciidoc/images/intelliJ-java-formatter-import.png b/sechub-doc/src/docs/asciidoc/images/intelliJ-java-formatter-import.png new file mode 100644 index 0000000000..5d4265796c Binary files /dev/null and b/sechub-doc/src/docs/asciidoc/images/intelliJ-java-formatter-import.png differ diff --git a/sechub-doc/src/docs/asciidoc/sechub-developer-quickstart-guide.adoc b/sechub-doc/src/docs/asciidoc/sechub-developer-quickstart-guide.adoc index f1879ecf39..c87c1a1889 100644 --- a/sechub-doc/src/docs/asciidoc/sechub-developer-quickstart-guide.adoc +++ b/sechub-doc/src/docs/asciidoc/sechub-developer-quickstart-guide.adoc @@ -256,13 +256,18 @@ Issues have labels a good way to start is to look for issues with https://github Issues with labels https://github.com/mercedes-benz/sechub/labels/beginner[`beginner`], https://github.com/mercedes-benz/sechub/labels/intermediate[`intermediate`], https://github.com/mercedes-benz/sechub/labels/advanced[`advanced`] indicate the level of difficulty. However, not all issues are labeled. In addition, if you like an issue just comment on it, so that we can assign you to it. -** In case, the issue you want to work on does not yet exists, please create an issue. +** In case, the issue you want to work on does not yet exist, please create an issue. . Create a new branch on your local fork following the naming pattern `feature--` + TIP: Example: `feature-36-go-client-supports-env-variable`. -. Implement your changes +. Set Up git configurations as explained here: https://mercedes-benz.github.io/sechub/latest/sechub-techdoc.html#sechub-git-configuration[Configure your git settings] + +. Set up your IDE + +. Implement your changes + +Please follow our coding conventions: https://mercedes-benz.github.io/sechub/latest/sechub-techdoc.html#section-coding-conventions[Coding conventions] + TIP: In case, you have any questions or need clarification ask inside the issues @@ -455,6 +460,10 @@ image::eclipse-import-gradle-projects-sechub-selected.png[] + image::eclipse-egradle-sechub-imported.png[] +==== Add Sechub Java Code Formatter + +include::documents/shared/configuration/eclipse-java-sechub-formatter-setup.adoc[] + ==== Switch to `Dark` theme (Optional) Needs to be done for every new workspace. @@ -471,7 +480,7 @@ image::eclipse-dark-theme.png[] . Press the `Apply and Close` button -== Set up IntelliJ for SecHub +=== Set up IntelliJ for SecHub Requirements: IntelliJ IDE (Community or Ultimate) is installed. @@ -487,6 +496,10 @@ image::intelliJ_javac_compiler.png[] + image::intelliJ_switch_to_intelliJ_compiler.png[] +==== Add Sechub Java Code Formatter + +include::documents/shared/configuration/intelliJ-java-sechub-formatter-setup.adoc[] + == Run Integration Tests from IDE === Run Integration Tests From Eclipse IDE diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/AsciidocGenerator.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/AsciidocGenerator.java index 409e3f762e..58f90fed04 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/AsciidocGenerator.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/AsciidocGenerator.java @@ -25,7 +25,7 @@ import com.mercedesbenz.sechub.docgen.usecase.UseCaseRestDocModel; import com.mercedesbenz.sechub.docgen.usecase.UseCaseRestDocModelAsciiDocGenerator; import com.mercedesbenz.sechub.docgen.util.ClasspathDataCollector; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; import com.mercedesbenz.sechub.pds.PDSStartupAssertEnvironmentVariablesUsed; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; @@ -35,7 +35,7 @@ public class AsciidocGenerator implements Generator { ClasspathDataCollector collector; - TextFileWriter writer = new TextFileWriter(); + DocGenTextFileWriter writer = new DocGenTextFileWriter(); /* ---------------------------------- */ /* ----- GENERATORS ----------------- */ @@ -146,11 +146,11 @@ private void generate(String path) throws IOException { private void generateAndCopySystemTestsDocFiles(GenContext context) throws IOException { String asciidoc = systemTestDocGenerator.generateDefaultFallbackTable(); File tableGenFile = new File(context.documentsGenFolder, "gen_systemtests_default_fallbacks_table.adoc"); - writer.save(tableGenFile, asciidoc); + writer.writeTextToFile(tableGenFile, asciidoc); asciidoc = systemTestDocGenerator.generateRuntimeVariableTable(); tableGenFile = new File(context.documentsGenFolder, "gen_systemtests_runtime_variables_table.adoc"); - writer.save(tableGenFile, asciidoc); + writer.writeTextToFile(tableGenFile, asciidoc); /* copy system test example files to doc */ File systemTestExampleGenFolder = new File("./../sechub-systemtest/build/gen/example"); @@ -166,17 +166,17 @@ private void generateSecHubModuleAndModuleGroupFiles(GenContext context) throws // module description String modulesTableGenData = moduleDescriptionTableGenerator.generate(); File modulesTableGenFile = new File(context.documentsGenFolder, "gen_modules_table.adoc"); - writer.save(modulesTableGenFile, modulesTableGenData); + writer.writeTextToFile(modulesTableGenFile, modulesTableGenData); // module group -> module String moduleGroupsTableGenData = moduleGroupToModuleTableGenerator.generate(); File moduleGroupToModuleTableGenFile = new File(context.documentsGenFolder, "gen_modulegroup_to_module_table.adoc"); - writer.save(moduleGroupToModuleTableGenFile, moduleGroupsTableGenData); + writer.writeTextToFile(moduleGroupToModuleTableGenFile, moduleGroupsTableGenData); // module -> modules group String moduleToModuleGroupTableGenData = moduleToModuleGroupTableGenerator.generate(); File moduleToModuleGroupTableGenFile = new File(context.documentsGenFolder, "gen_module_to_modulegroup_table.adoc"); - writer.save(moduleToModuleGroupTableGenFile, moduleToModuleGroupTableGenData); + writer.writeTextToFile(moduleToModuleGroupTableGenFile, moduleToModuleGroupTableGenData); } @@ -224,7 +224,7 @@ private void generateCheckmarxPDSSolutionParts(GenContext context) throws IOExce File clientGenDocFolder = new File(context.documentsGenFolder, "pds-solutions"); File targetFile = new File(clientGenDocFolder, "gen_checkmarx_wrapper_env_and_job_parameter_table.adoc"); - writer.save(targetFile, table); + writer.writeTextToFile(targetFile, table); } private void generateClientParts(GenContext context) throws IOException { @@ -232,7 +232,7 @@ private void generateClientParts(GenContext context) throws IOException { String defaultZipAllowedFilePatternsTable = clientDocFilesGenerator.generateDefaultZipAllowedFilePatternsTable(); File clientGenDocFolder = new File(context.documentsGenFolder, "client"); File targetFile = new File(clientGenDocFolder, "gen_table_default_zip_allowed_file_patterns.adoc"); - writer.save(targetFile, defaultZipAllowedFilePatternsTable); + writer.writeTextToFile(targetFile, defaultZipAllowedFilePatternsTable); } private void generateExampleFiles(GenContext context) throws IOException { @@ -244,7 +244,7 @@ private void generateExampleFiles(GenContext context) throws IOException { private void generateExample(String endingfileName, File documentsGenFolder, String content) throws IOException { File examplesFolder = new File(documentsGenFolder, "examples"); File targetFile = new File(examplesFolder, "gen_example_" + endingfileName); - writer.save(targetFile, content); + writer.writeTextToFile(targetFile, content); } private void generateSecHubProfilesOverview(GenContext context) throws IOException { @@ -271,7 +271,7 @@ private void generateSpringProfilePlantUML(File diagramsGenFolder, SpringProfile } String text = geno.generate(config); File targetFile = new File(diagramsGenFolder, "gen_springprofiles" + addition + ".puml"); - writer.save(targetFile, text); + writer.writeTextToFile(targetFile, text); } private void generateMessagingFiles(GenContext context) throws IOException { @@ -286,11 +286,11 @@ private void generateUseCaseFiles(GenContext context) throws IOException { String useCaseAsciidoc = useCaseModelAsciiDocGenerator.generateAsciidoc(model, context.diagramsGenFolder); File targetFile = new File(context.documentsGenFolder, "gen_usecases.adoc"); - writer.save(targetFile, useCaseAsciidoc); + writer.writeTextToFile(targetFile, useCaseAsciidoc); String usecaseRestDoc = useCaseRestDocModelAsciiDocGenerator.generateAsciidoc(writer, restDocModel, true, UseCaseIdentifier.values()); File targetFile2 = new File(context.documentsGenFolder, "gen_uc_restdoc.adoc"); - writer.save(targetFile2, usecaseRestDoc); + writer.writeTextToFile(targetFile2, usecaseRestDoc); } private void generatePDSUseCaseFiles(GenContext context) throws IOException { @@ -299,7 +299,7 @@ private void generatePDSUseCaseFiles(GenContext context) throws IOException { String useCaseAsciidoc = useCaseModelAsciiDocGenerator.generateAsciidoc(model, context.diagramsGenFolder, false, false); File targetFile = new File(context.documentsGenFolder, "gen_pds-usecases.adoc"); - writer.save(targetFile, useCaseAsciidoc); + writer.writeTextToFile(targetFile, useCaseAsciidoc); } static void output(String text) { @@ -348,32 +348,32 @@ public void fetchDomainMessagingParts() { public void generateSecHubSystemPropertiesDescription(GenContext context) throws IOException { String text = propertiesGenerator.generate(getCollector().fetchMustBeDocumentParts(), context.sechubEnvVariableRegistry); - writer.save(context.systemProperitesFile, text); + writer.writeTextToFile(context.systemProperitesFile, text); } public void generatePDSSystemPropertiesDescription(GenContext context) throws IOException { String text = propertiesGenerator.generate(getCollector().fetchPDSMustBeDocumentParts(), context.pdsEnvVariableRegistry); - writer.save(context.pdsSystemProperitesFile, text); + writer.writeTextToFile(context.pdsSystemProperitesFile, text); } private void generatePDSExecutorConfigurationParamters(GenContext context) throws IOException { String text = pdsExecutorConfigParameterGenerator.generatePDSExecutorConfigurationParamters(context.pdsPDSExecutorConfigParametersFile); - writer.save(context.pdsPDSExecutorConfigParametersFile, text); + writer.writeTextToFile(context.pdsPDSExecutorConfigParametersFile, text); } public void generateSecHubJavaLaunchExample(GenContext context) throws IOException { String text = javaLaunchExampleGenerator.generate(getCollector().fetchMustBeDocumentParts(), context.sechubEnvVariableRegistry); - writer.save(context.javaLaunchExampleFile, text); + writer.writeTextToFile(context.javaLaunchExampleFile, text); } public void generateSecHubScheduleDescription(GenContext context) throws IOException { String text = scheduleDescriptionGenerator.generate(getCollector()); - writer.save(context.scheduleDescriptionFile, text); + writer.writeTextToFile(context.scheduleDescriptionFile, text); } private void generateSecHubMockPropertiesDescription(GenContext context) throws IOException { String text = propertiesGenerator.generate(getCollector().fetchMockAdapterSpringValueDocumentationParts(), context.sechubEnvVariableRegistry); - writer.save(context.specialMockValuePropertiesFile, text); + writer.writeTextToFile(context.specialMockValuePropertiesFile, text); } private ClasspathDataCollector getCollector() { diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/kubernetes/KubernetesTemplateFilesGenerator.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/kubernetes/KubernetesTemplateFilesGenerator.java index fbed326b81..7ac4830ab3 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/kubernetes/KubernetesTemplateFilesGenerator.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/kubernetes/KubernetesTemplateFilesGenerator.java @@ -16,7 +16,7 @@ import com.mercedesbenz.sechub.docgen.spring.SpringValueExtractor; import com.mercedesbenz.sechub.docgen.spring.SpringValueExtractor.SpringValue; import com.mercedesbenz.sechub.docgen.util.ClasspathDataCollector; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; class KubernetesTemplateFilesGenerator implements Generator { @@ -27,12 +27,12 @@ class KubernetesTemplateFilesGenerator implements Generator { SpringValueExtractor springValueExtractor; SpringScheduleExtractor springScheduledExtractor; - TextFileWriter writer; + DocGenTextFileWriter writer; public KubernetesTemplateFilesGenerator() { this.springValueExtractor = new SpringValueExtractor(); this.springScheduledExtractor = new SpringScheduleExtractor(); - this.writer = new TextFileWriter(); + this.writer = new DocGenTextFileWriter(); } public static void main(String[] args) throws Exception { @@ -59,10 +59,10 @@ public void generate(KubernetesFiles result, List list) throw list.add(newSecret("sechub.server.ssl.keystore.file", "ssl", "The server ssl certificate file", "server-certificate.p12")); /* - * config (normally unnecessary because automatical generated, but we want .json - * as file ending, so here necessary + * configuration (normally unnecessary because automatical generated, but we + * want .json as file ending, so here necessary */ - list.add(newSecret("sechub.scan.config.initial", "config", "The initial scan configuration", "sechub_scan_config_initial.json")); + list.add(newSecret("sechub.scan.config.initial", "configuration", "The initial scan configuration", "sechub_scan_config_initial.json")); Collections.sort(list); generateDeploymentFilePart(result, list); @@ -71,7 +71,7 @@ public void generate(KubernetesFiles result, List list) throw generateSecretShellScriptsAndMissingSecretFiles(result); /* last but not least create output file */ File generatedDeploymentFilePart = new File(ensureKubernetesGenFolder(), "sechub-server-environment-deployment-parts.gen.yaml"); - writer.save(generatedDeploymentFilePart, result.serverDeploymentYaml); + writer.writeTextToFile(generatedDeploymentFilePart, result.serverDeploymentYaml); output("Written generated deployment file:" + generatedDeploymentFilePart); } @@ -213,7 +213,7 @@ private void createSecretShellScriptAndFiles(KubernetesFiles result, File target String shellName = createShellScriptName(secretName); allShellScriptNames.add(shellName); File targetFile = new File(targetFolder, shellName); - writer.save(targetFile, sb.toString(), true); + writer.writeTextToFile(targetFile, sb.toString(), true); output("Updated/Created:" + targetFile); } @@ -253,7 +253,7 @@ private void createSecretShellScriptAndFiles(KubernetesFiles result, File target sb.append("./").append(shellScriptName).append("\n"); } File targetFile = new File(targetFolder, createShellScriptName("all")); - writer.save(targetFile, sb.toString(), true); + writer.writeTextToFile(targetFile, sb.toString(), true); output("Updated/Created:" + targetFile); } diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/DomainMessagingFilesGenerator.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/DomainMessagingFilesGenerator.java index 44e447cad3..865b9d5040 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/DomainMessagingFilesGenerator.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/DomainMessagingFilesGenerator.java @@ -8,15 +8,15 @@ import com.mercedesbenz.sechub.docgen.messaging.DomainMessagingModel.Domain; import com.mercedesbenz.sechub.docgen.messaging.DomainMessagingModel.DomainPart; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; public class DomainMessagingFilesGenerator { - private TextFileWriter writer; + private DocGenTextFileWriter writer; private DomainMessagingModelPlantUMLGenerator domainMessagingModelPlantUMLGenerator; - public DomainMessagingFilesGenerator(TextFileWriter writer) { + public DomainMessagingFilesGenerator(DocGenTextFileWriter writer) { this.writer = writer; this.domainMessagingModelPlantUMLGenerator = new DomainMessagingModelPlantUMLGenerator(); } @@ -57,7 +57,7 @@ public void generateMessagingFiles(File messagingFile, File diagramsGenFolder, D sb.append("\n"); sb.append("include::message2usecases_" + createMessagingLinkId(id) + ".adoc[]\n"); } - writer.save(messagingFile, sb.toString()); + writer.writeTextToFile(messagingFile, sb.toString()); } public static String createMessagingLinkId(MessageID messageId) { @@ -76,7 +76,7 @@ private void generateMessagingPlantumlFile(File diagramsGenFolder, DomainMessagi MessageID[] messageIdsToInspect) throws IOException { String generatedPlantuml = domainMessagingModelPlantUMLGenerator.generate(model, title, messageIdsToInspect, true); File targetFile = new File(diagramsGenFolder, targetFileName); - writer.save(targetFile, generatedPlantuml); + writer.writeTextToFile(targetFile, generatedPlantuml); } public DomainMessagingModel createReducedClone(DomainMessagingModel originModel) { diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventMessageLinkAsciidocGenerator.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventMessageLinkAsciidocGenerator.java index 7744086448..e9ed37957c 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventMessageLinkAsciidocGenerator.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventMessageLinkAsciidocGenerator.java @@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory; import com.mercedesbenz.sechub.docgen.usecase.UseCaseAsciiDocFactory; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; @@ -25,14 +25,14 @@ public class UseCaseEventMessageLinkAsciidocGenerator { private static final Logger LOG = LoggerFactory.getLogger(UseCaseEventMessageLinkAsciidocGenerator.class); - private TextFileWriter writer; + private DocGenTextFileWriter writer; private File outputFolder; private Map> usecaseToMessageIdMap = new TreeMap<>(); public UseCaseEventMessageLinkAsciidocGenerator(Map> usecaseToMessageIdMap, File outputFolder) { this.outputFolder = outputFolder; this.usecaseToMessageIdMap = usecaseToMessageIdMap; - this.writer = new TextFileWriter(); + this.writer = new DocGenTextFileWriter(); } public void generate() throws IOException { @@ -52,7 +52,7 @@ private void generateLinkUsecaseToMessagesFile(UseCaseIdentifier identifier) thr String id = UseCaseEventOverviewPlantUmlGenerator.createUsecaseId(identifier); File file = new File(outputFolder, "usecase2messages_" + id + ".adoc"); - writer.save(file, ab.getAsciiDoc()); + writer.writeTextToFile(file, ab.getAsciiDoc()); } private AsciidocBuilder generateLinkUsecaseToMessagesContent(UseCaseIdentifier identifier) { @@ -78,7 +78,7 @@ private void generateLinkMessageToUsecasesFile(MessageID identifier) throws IOEx String id = DomainMessagingFilesGenerator.createMessagingLinkId(identifier); File file = new File(outputFolder, "message2usecases_" + id + ".adoc"); - writer.save(file, ab.getAsciiDoc()); + writer.writeTextToFile(file, ab.getAsciiDoc()); } private AsciidocBuilder generateLinkMessageToUsecasesContent(MessageID identifier) { diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventOverviewPlantUmlGenerator.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventOverviewPlantUmlGenerator.java index 2bdefe5a62..4b53c541a7 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventOverviewPlantUmlGenerator.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/messaging/UseCaseEventOverviewPlantUmlGenerator.java @@ -14,8 +14,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.mercedesbenz.sechub.docgen.util.TextFileReader; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileReader; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; import com.mercedesbenz.sechub.sharedkernel.messaging.IntegrationTestEventHistory; import com.mercedesbenz.sechub.sharedkernel.messaging.IntegrationTestEventHistoryInspection; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; @@ -34,16 +34,16 @@ public class UseCaseEventOverviewPlantUmlGenerator { private static final Logger LOG = LoggerFactory.getLogger(UseCaseEventOverviewPlantUmlGenerator.class); private File folderToStartFrom; - private TextFileReader reader; - private TextFileWriter writer; + private DocGenTextFileReader reader; + private DocGenTextFileWriter writer; private File outputFolder; private Map> usecaseNameToMessageIdsMap = new TreeMap<>(); public UseCaseEventOverviewPlantUmlGenerator(File jsonEventDataFolder, File outputFolder) { this.folderToStartFrom = jsonEventDataFolder; this.outputFolder = outputFolder; - this.reader = new TextFileReader(); - this.writer = new TextFileWriter(); + this.reader = new DocGenTextFileReader(); + this.writer = new DocGenTextFileWriter(); } /** @@ -115,7 +115,7 @@ private void generateFile(File jsonSourceFile) { if (jsonSourceFile.getAbsolutePath().endsWith("uc_admin_enables_scheduler_job_processing.json")) { System.out.println("got"); } - String json = reader.loadTextFile(jsonSourceFile); + String json = reader.readTextFromFile(jsonSourceFile); IntegrationTestEventHistory history = IntegrationTestEventHistory.fromJSONString(json); try { generate(history, usecaseIdentifier, variant); @@ -191,7 +191,7 @@ private void generate(IntegrationTestEventHistory history, UseCaseIdentifier use /* write PLANTUML file */ File targetFile = new File(outputFolder, createPlantumlFileSubPathByUsecase(usecaseIdentifier.name(), variant)); - writer.save(targetFile, pb.getPlantUML()); + writer.writeTextToFile(targetFile, pb.getPlantUML()); } diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/spring/SystemPropertiesDescriptionGenerator.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/spring/SystemPropertiesDescriptionGenerator.java index 44af665883..aa05dbef87 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/spring/SystemPropertiesDescriptionGenerator.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/spring/SystemPropertiesDescriptionGenerator.java @@ -74,7 +74,7 @@ protected String generate(List list, SecureEnvironmentVariabl protected void appendStringContent(StringBuilder sb, Map> rowMap) { for (Map.Entry> entries : rowMap.entrySet()) { SortedSet table = entries.getValue(); - sb.append("[[section-gen-config-scope-").append(entries.getKey()).append("]]\n"); + sb.append("[[section-gen-configuration-scope-").append(entries.getKey()).append("]]\n"); sb.append("[options=\"header\",cols=\"1,1,1\"]\n"); sb.append(".").append(buildTitle(entries.getKey())); sb.append("\n|===\n"); diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelAsciiDocGenerator.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelAsciiDocGenerator.java index 791d99785a..ee271aa269 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelAsciiDocGenerator.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelAsciiDocGenerator.java @@ -16,8 +16,8 @@ import com.mercedesbenz.sechub.docgen.AsciidocUtil; import com.mercedesbenz.sechub.docgen.RestDocResourceModel; import com.mercedesbenz.sechub.docgen.usecase.UseCaseModel.UseCaseEntry; -import com.mercedesbenz.sechub.docgen.util.TextFileReader; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileReader; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseGroup; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseRestDoc; @@ -25,7 +25,7 @@ public class UseCaseRestDocModelAsciiDocGenerator { - private TextFileReader reader = new TextFileReader(); + private DocGenTextFileReader reader = new DocGenTextFileReader(); // [options="header",cols="1,1,1"] // |=== @@ -35,7 +35,7 @@ public class UseCaseRestDocModelAsciiDocGenerator { // |Row2A |Row2B |Row2Cs // |Row3A |Row3B |Row3C // |=== - public String generateAsciidoc(TextFileWriter writer, UseCaseRestDocModel model, boolean technical, UseCaseIdentifier... usecases) { + public String generateAsciidoc(DocGenTextFileWriter writer, UseCaseRestDocModel model, boolean technical, UseCaseIdentifier... usecases) { Objects.requireNonNull(writer); Objects.requireNonNull(model); @@ -170,7 +170,7 @@ private void appendRestDefinition(Context context, UseCaseRestDocEntry entry, Ma private void appendGeneralRequestInformation(Context context, Map map) { File resourceFile = map.get(SpringRestDocOutput.RESOURCE); if (resourceFile != null) { - String json = reader.loadTextFile(resourceFile); + String json = reader.readTextFromFile(resourceFile); RestDocResourceModel model = RestDocResourceModel.fromString(json); context.addLine("[options=\"header\",cols=\"1,6\",title=\"General request information\"]"); @@ -236,7 +236,7 @@ private void addDescription(Context context, UseCaseRestDocEntry entry, File fil private boolean isGeneratedFileEmpty(File file) { boolean justEmpty = false; if (SpringRestDocOutput.RESPONSE_BODY.isRepresentedBy(file)) { - String content = reader.loadTextFile(file, "\n"); + String content = reader.readTextFromFile(file); justEmpty = AsciidocUtil.isEmptyAsciidocContent(content); } return justEmpty; diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGenTextFileReader.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGenTextFileReader.java new file mode 100644 index 0000000000..0bceca11e5 --- /dev/null +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGenTextFileReader.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.docgen.util; + +import java.io.File; + +import com.mercedesbenz.sechub.commons.TextFileReader; + +public class DocGenTextFileReader { + + private TextFileReader reader = new TextFileReader(); + + /** + * Read text from file + * + * @param file file to read from + * @return text, never null + * + * @throws IllegalStateException if file cannot be read. + */ + public String readTextFromFile(File file) { + try { + return reader.readTextFromFile(file); + } catch (Exception e) { + throw new IllegalStateException("Doc generation / test case corrupt: Cannot read test file " + file.getAbsolutePath(), e); + } + } +} diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/TextFileWriter.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGenTextFileWriter.java similarity index 64% rename from sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/TextFileWriter.java rename to sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGenTextFileWriter.java index 26a9098a87..ac36784cad 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/TextFileWriter.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/DocGenTextFileWriter.java @@ -1,55 +1,26 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.docgen.util; -import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.nio.file.Files; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mercedesbenz.sechub.docgen.GeneratorConstants; -public class TextFileWriter { +public class DocGenTextFileWriter { - private static final Logger LOG = LoggerFactory.getLogger(TextFileWriter.class); + private static final Logger LOG = LoggerFactory.getLogger(DocGenTextFileWriter.class); private LicenseHeaderProvider licenseHeaderProvider; - public TextFileWriter() { + public DocGenTextFileWriter() { licenseHeaderProvider = new LicenseHeaderProvider(); } - public void addMissingHeaders(File targetFile) throws IOException { - /* read content */ - String origin = read(targetFile); - if (origin.contains(LicenseHeaderProvider.LICENSE_SPDX_IDENTIFIER)) { - /* already contained - ignore */ - return; - } - /* do save, will add header if necessary */ - save(targetFile, origin, true); - } - - private String read(File file) throws IOException { - StringBuilder sb = new StringBuilder(); - try (BufferedReader br = Files.newBufferedReader(file.toPath())) { - String readLine = null; - boolean notFirstLine = false; - while ((readLine = br.readLine()) != null) { - if (notFirstLine) { - sb.append("\n"); - } - notFirstLine = true; - sb.append(readLine); - } - } - return sb.toString(); - } - /** * Save text file, does overwrite existing ones! Adds missing headers * @@ -57,8 +28,8 @@ private String read(File file) throws IOException { * @param origin * @throws IOException */ - public void save(File targetFile, String origin) throws IOException { - save(targetFile, origin, true); + public void writeTextToFile(File targetFile, String origin) throws IOException { + writeTextToFile(targetFile, origin, true); } /** @@ -70,7 +41,7 @@ public void save(File targetFile, String origin) throws IOException { * @param overwrite * @throws IOException */ - public void save(File targetFile, String origin, boolean overwrite) throws IOException { + public void writeTextToFile(File targetFile, String origin, boolean overwrite) throws IOException { if (targetFile == null) { throw new IllegalArgumentException("null not allowed as file!"); } diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/RestDocFactory.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/RestDocFactory.java index 30453a6b36..f88b7da5ad 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/RestDocFactory.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/RestDocFactory.java @@ -120,4 +120,5 @@ public static String extractTag(String apiEndpoint) { return tag; } + } \ No newline at end of file diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/TextFileReader.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/TextFileReader.java deleted file mode 100644 index ea80b084a3..0000000000 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/util/TextFileReader.java +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.docgen.util; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; - -public class TextFileReader { - - public String loadTextFile(File file) { - return loadTextFile(file, "\n"); - } - - public String loadTextFile(File file, String lineBreak) { - StringBuilder sb = new StringBuilder(); - - try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { - String line = null; - - boolean firstEntry = true; - while ((line = br.readLine()) != null) { - if (!firstEntry) { - sb.append(lineBreak); - } - sb.append(line); - firstEntry = false;// this prevents additional line break at end of file... - } - return sb.toString(); - } catch (Exception e) { - throw new IllegalStateException("Testcase corrupt: Cannot read test file " + file.getAbsolutePath(), e); - } - } -} diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java index 8570efd7fd..e4e7a9dd6a 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java @@ -33,7 +33,7 @@ class ExampleFilesValidTest { void check_pds_config_example1_can_be_loaded_and_is_valid() throws Exception { /* execute */ - String json = TestFileReader.loadTextFile("src/docs/asciidoc/documents/pds/product_delegation_server_config_example1.json"); + String json = TestFileReader.readTextFromFile("src/docs/asciidoc/documents/pds/product_delegation_server_config_example1.json"); PDSServerConfiguration configuration = PDSServerConfiguration.fromJSON(json); /* test */ @@ -66,7 +66,7 @@ void check_pds_config_example1_can_be_loaded_and_is_valid() throws Exception { @EnumSource(ExampleFile.class) void every_sechub_config_file_is_valid(ExampleFile file) { /* prepare */ - String json = TestFileReader.loadTextFile(file.getPath()); + String json = TestFileReader.readTextFromFile(file.getPath()); SecHubScanConfiguration config = null; /* execute */ @@ -85,14 +85,14 @@ void every_sechub_config_file_is_valid(ExampleFile file) { "WEBSCAN_OPENAPI_WITH_DATA_REFERENCE" }, mode = EnumSource.Mode.INCLUDE) void every_sechub_config_webscan_file_is_valid_and_has_a_target_uri(ExampleFile file) { /* prepare */ - String json = TestFileReader.loadTextFile(file.getPath()); + String json = TestFileReader.readTextFromFile(file.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); /* test */ Optional webScanOpt = config.getWebScan(); - assertTrue(webScanOpt.isPresent(), "Webscan config does exist for file: " + file.getPath()); + assertTrue(webScanOpt.isPresent(), "Webscan configuration does exist for file: " + file.getPath()); SecHubWebScanConfiguration webScan = webScanOpt.get(); assertNotNull(webScan.getUrl(), "No URI set in file: " + file.getPath()); diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/AsciidocGeneratorTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/AsciidocGeneratorTest.java index d129cd32a0..e45ad83beb 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/AsciidocGeneratorTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/AsciidocGeneratorTest.java @@ -14,7 +14,7 @@ import com.mercedesbenz.sechub.docgen.spring.ScheduleDescriptionGenerator; import com.mercedesbenz.sechub.docgen.spring.SystemPropertiesDescriptionGenerator; import com.mercedesbenz.sechub.docgen.util.ClasspathDataCollector; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; public class AsciidocGeneratorTest { @@ -26,7 +26,7 @@ public void before() throws Exception { generatorToTest.collector = mock(ClasspathDataCollector.class); generatorToTest.propertiesGenerator = mock(SystemPropertiesDescriptionGenerator.class); generatorToTest.scheduleDescriptionGenerator = mock(ScheduleDescriptionGenerator.class); - generatorToTest.writer = mock(TextFileWriter.class); + generatorToTest.writer = mock(DocGenTextFileWriter.class); when(generatorToTest.propertiesGenerator.generate(any(), any())).thenReturn("properties-test"); when(generatorToTest.scheduleDescriptionGenerator.generate(generatorToTest.collector)).thenReturn("schedule-test"); @@ -70,7 +70,7 @@ public void calls_properties_generator_and_saves() throws Exception { /* test */ verify(generatorToTest.propertiesGenerator).generate(any(), any()); - verify(generatorToTest.writer).save(genContext.systemProperitesFile, "properties-test"); + verify(generatorToTest.writer).writeTextToFile(genContext.systemProperitesFile, "properties-test"); } @@ -86,7 +86,7 @@ public void calls_schedule_generator_and_saves() throws Exception { /* test */ verify(generatorToTest.scheduleDescriptionGenerator).generate(generatorToTest.collector); - verify(generatorToTest.writer).save(genContext.scheduleDescriptionFile, "schedule-test"); + verify(generatorToTest.writer).writeTextToFile(genContext.scheduleDescriptionFile, "schedule-test"); } diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/RestDocResourceModelTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/RestDocResourceModelTest.java index 3029c463a9..7abf8a876a 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/RestDocResourceModelTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/RestDocResourceModelTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -import com.mercedesbenz.sechub.docgen.util.TextFileReader; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileReader; class RestDocResourceModelTest { @@ -15,8 +15,8 @@ class RestDocResourceModelTest { void test() { /* prepare */ File file = new File("./src/test/resources/restdoc/test_resource_1.json"); - TextFileReader reader = new TextFileReader(); - String json = reader.loadTextFile(file); + DocGenTextFileReader reader = new DocGenTextFileReader(); + String json = reader.readTextFromFile(file); /* execute */ RestDocResourceModel model = RestDocResourceModel.fromString(json); diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/adopt/AdoptionChecker.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/adopt/AdoptionChecker.java index b9e5276aaa..c014a91885 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/adopt/AdoptionChecker.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/adopt/AdoptionChecker.java @@ -28,13 +28,13 @@ public void assertAdoptedClassEqualsFileLocatedAt(String packageName, String cla */ File adoptedFile = new File("./src/main/java/" + javaClassNameToPath(adoptedClass)); - String adopted = reader.loadTextFile(adoptedFile); + String adopted = reader.readTextFromFile(adoptedFile); String adoptedChanged = adopted.replaceAll(Pattern.quote(adoptedClass.getSimpleName()), Matcher.quoteReplacement(className)); adoptedChanged = adoptedChanged.replaceAll(Pattern.quote(adoptedClass.getPackageName()), Matcher.quoteReplacement(packageName)); String pathname = "./../sechub-systemtest/src/main/java/" + javaClassToPath(packageName, className); File originFile = new File(pathname); - String origin = reader.loadTextFile(originFile); + String origin = reader.readTextFromFile(originFile); String originReduced = withoutCommentsOrEmptyLines(origin); String adoptedReduced = withoutCommentsOrEmptyLines(adoptedChanged); diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/util/TextFileWriterTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/util/TextFileWriterTest.java index d35debd9f7..cf67737cb1 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/util/TextFileWriterTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/docgen/util/TextFileWriterTest.java @@ -14,11 +14,11 @@ public class TextFileWriterTest { - private TextFileWriter writerToTest; + private DocGenTextFileWriter writerToTest; @BeforeEach void before() throws Exception { - writerToTest = new TextFileWriter(); + writerToTest = new DocGenTextFileWriter(); } @Test @@ -33,7 +33,7 @@ void is_able_to_save_a_file_having_path_not_createad_before() throws Exception { assertFalse(targetFile.exists()); /* execute */ - writerToTest.save(targetFile, "text"); + writerToTest.writeTextToFile(targetFile, "text"); /* test */ assertTrue(subFolder.exists()); diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/DownloadsFullScanDataForJobRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/DownloadsFullScanDataForJobRestDocTest.java index 1f47195d20..d6c4772377 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/DownloadsFullScanDataForJobRestDocTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/DownloadsFullScanDataForJobRestDocTest.java @@ -79,8 +79,7 @@ public void before() { d.metaData = "{}"; data.allScanData.add(d); - String config = "{}"; - ProjectScanLog log = new ProjectScanLog("theProject", jobUUID, "spartakus", config); + ProjectScanLog log = new ProjectScanLog("theProject", jobUUID, "spartakus"); data.allScanLogs.add(log); when(fullScanDataService.getFullScanData(jobUUID)).thenReturn(data); diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/EncryptionAdministrationRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/EncryptionAdministrationRestControllerRestDocTest.java new file mode 100644 index 0000000000..9f1179d55c --- /dev/null +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/EncryptionAdministrationRestControllerRestDocTest.java @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.restdoc; + +import static com.mercedesbenz.sechub.docgen.util.RestDocFactory.*; +import static com.mercedesbenz.sechub.restdoc.RestDocumentation.*; +import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.*; +import static org.mockito.Mockito.*; +import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.lang.annotation.Annotation; +import java.time.LocalDateTime; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.mercedesbenz.sechub.commons.model.job.ExecutionState; +import com.mercedesbenz.sechub.domain.administration.encryption.AdministrationEncryptionRotationService; +import com.mercedesbenz.sechub.domain.administration.encryption.AdministrationEncryptionStatusService; +import com.mercedesbenz.sechub.domain.administration.encryption.EncryptionAdministrationRestController; +import com.mercedesbenz.sechub.domain.administration.job.JobAdministrationRestController; +import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.configuration.AbstractSecHubAPISecurityConfiguration; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionDataValidator; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubPasswordSource; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseRestDoc; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminFetchesEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminStartsEncryptionRotation; +import com.mercedesbenz.sechub.test.ExampleConstants; +import com.mercedesbenz.sechub.test.TestIsNecessaryForDocumentation; +import com.mercedesbenz.sechub.test.TestPortProvider; + +@RunWith(SpringRunner.class) +@WebMvcTest(JobAdministrationRestController.class) +@ContextConfiguration(classes = { EncryptionAdministrationRestController.class, SecHubEncryptionDataValidator.class, + EncryptionAdministrationRestControllerRestDocTest.SimpleTestConfiguration.class }) +@WithMockUser(roles = RoleConstants.ROLE_SUPERADMIN) +@ActiveProfiles({ Profiles.TEST, Profiles.ADMIN_ACCESS }) +@AutoConfigureRestDocs(uriScheme = "https", uriHost = ExampleConstants.URI_SECHUB_SERVER, uriPort = 443) +public class EncryptionAdministrationRestControllerRestDocTest implements TestIsNecessaryForDocumentation { + + private static final int PORT_USED = TestPortProvider.DEFAULT_INSTANCE.getRestDocTestPort(); + + @Autowired + private MockMvc mockMvc; + + @MockBean + AdministrationEncryptionRotationService encryptionRotationService; + + @MockBean + AdministrationEncryptionStatusService encryptionStatusService; + + @Before + public void before() { + + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminStartsEncryptionRotation.class) + public void restdoc_admin_starts_encryption_rotation() throws Exception { + + /* prepare */ + + SecHubEncryptionData data = new SecHubEncryptionData(); + data.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_256); + data.setPasswordSourceType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + data.setPasswordSourceData("SECRET_1"); + + String apiEndpoint = https(PORT_USED).buildAdminStartsEncryptionRotation(); + Class useCase = UseCaseAdminStartsEncryptionRotation.class; + + /* execute + test @formatter:off */ + + this.mockMvc.perform( + post(apiEndpoint). + contentType(MediaType.APPLICATION_JSON_VALUE). + content(data.toFormattedJSON()). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(extractTag(apiEndpoint)). + and(). + document( + requestHeaders( + + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminFetchesEncryptionStatus.class) + public void restdoc_admin_fetches_encryption_status() throws Exception { + + /* prepare */ + + SecHubEncryptionStatus status = createEncryptionStatusExample(); + + when(encryptionStatusService.fetchStatus()).thenReturn(status); + + String apiEndpoint = https(PORT_USED).buildAdminFetchesEncryptionStatus(); + Class useCase = UseCaseAdminFetchesEncryptionStatus.class; + + /* execute + test @formatter:off */ + String domains = SecHubEncryptionStatus.PROPERTY_DOMAINS+"[]."; + String domainData = domains+SecHubDomainEncryptionStatus.PROPERTY_DATA+"[]."; + + this.mockMvc.perform( + get(apiEndpoint). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(extractTag(apiEndpoint)). + responseSchema(OpenApiSchema.ENCRYPTION_STATUS.getSchema()). + and(). + document( + requestHeaders( + + ), + responseFields( + fieldWithPath(SecHubEncryptionStatus.PROPERTY_TYPE).description("The type description of the json content"), + fieldWithPath(domains+SecHubDomainEncryptionStatus.PROPERTY_NAME).description("Name of the domain which will provide this encryption data elements"), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_ID).description("Unique identifier"), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_ALGORITHM).description("Algorithm used for encryption"), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_PASSWORDSOURCE+"."+ SecHubPasswordSource.PROPERTY_TYPE).description("Type of password source. Can be "+List.of(SecHubCipherPasswordSourceType.values())), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_PASSWORDSOURCE+"."+ SecHubPasswordSource.PROPERTY_DATA).description("Data for password source. If type is "+SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE+" then it is the the name of the environment variable."), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_USAGE).description("Map containing information about usage of this encryption"), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_USAGE+".*").description("Key value data"), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_CREATED).description("Creation timestamp"), + fieldWithPath(domainData+SecHubDomainEncryptionData.PROPERTY_CREATED_FROM).description("User id of admin who created the encryption entry") + ) + )); + + /* @formatter:on */ + } + + private SecHubEncryptionStatus createEncryptionStatusExample() { + SecHubEncryptionStatus status = new SecHubEncryptionStatus(); + SecHubDomainEncryptionStatus scheduleDomainEncryptionStatus = new SecHubDomainEncryptionStatus(); + scheduleDomainEncryptionStatus.setName("schedule"); + + // create some example domain encryption data like in really + SecHubDomainEncryptionData scheduleDomainEncryptionData = new SecHubDomainEncryptionData(); + scheduleDomainEncryptionData.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_256); + scheduleDomainEncryptionData.setCreated(LocalDateTime.of(2024, 8, 1, 9, 26)); + scheduleDomainEncryptionData.setCreatedFrom("admin-username"); + scheduleDomainEncryptionData.setId("1"); + scheduleDomainEncryptionData.getPasswordSource().setType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + scheduleDomainEncryptionData.getPasswordSource().setData("SECRET_1"); + + long value = 1; + for (ExecutionState state : ExecutionState.values()) { + scheduleDomainEncryptionData.getUsage().put("job.state." + state.name().toLowerCase(), value++); + } + + scheduleDomainEncryptionStatus.getData().add(scheduleDomainEncryptionData); + status.getDomains().add(scheduleDomainEncryptionStatus); + return status; + } + + @EnableAutoConfiguration + public static class SimpleTestConfiguration extends AbstractSecHubAPISecurityConfiguration { + + } + +} diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/JobAdministrationRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/JobAdministrationRestControllerRestDocTest.java index 2c9b197ab5..29d03d5115 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/JobAdministrationRestControllerRestDocTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/JobAdministrationRestControllerRestDocTest.java @@ -81,7 +81,6 @@ public void before() { info.setStatus(JobStatus.RUNNING); info.setProjectId("project-name"); - info.setConfiguration("{ config data }"); info.setOwner("owner-userid"); info.setSince(LocalDateTime.now()); @@ -119,8 +118,7 @@ public void restdoc_list_all_running_jobs() throws Exception { fieldWithPath(inArray(JobInformation.PROPERTY_PROJECT_ID)).description("The name of the project the job is running for"), fieldWithPath(inArray(JobInformation.PROPERTY_OWNER)).description("Owner of the job - means user which triggered it"), fieldWithPath(inArray(JobInformation.PROPERTY_STATUS)).description("A status information "), - fieldWithPath(inArray(JobInformation.PROPERTY_SINCE)).description("Timestamp since when job has been started"), - fieldWithPath(inArray(JobInformation.PROPERTY_CONFIGURATION)).description("Configuration used for this job") + fieldWithPath(inArray(JobInformation.PROPERTY_SINCE)).description("Timestamp since when job has been started") ) )); @@ -227,7 +225,7 @@ public void restdoc_restart_job_hard() throws Exception { } // see - // https://docs.spring.io/spring-restdocs/docs/current/reference/html5/#documenting-your-api-request-response-payloads-fields-json + // https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/#documenting-your-api-request-response-payloads-fields-json private static String inArray(String field) { return "[]." + field; } diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java index 38c95ee49d..df032df5e6 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java @@ -64,6 +64,8 @@ enum OpenApiSchema { PROJECT_JOB_LIST("ProjectJobList"), + ENCRYPTION_STATUS("EncryptionStatus"), + ; private final Schema schema; diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutionProfileRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutionProfileRestControllerRestDocTest.java index 7c2b34c16c..a9f5c9fe6d 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutionProfileRestControllerRestDocTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutionProfileRestControllerRestDocTest.java @@ -336,7 +336,7 @@ public void restdoc_admin_fetches_profile() throws Exception { fieldWithPath(PROPERTY_ENABLED).description("Enabled state of profile, default is false").optional(), fieldWithPath(PROPERTY_CONFIGURATIONS+"[]."+ProductExecutorConfig.PROPERTY_UUID).description("uuid of configuration"), fieldWithPath(PROPERTY_CONFIGURATIONS+"[]."+ProductExecutorConfig.PROPERTY_NAME).description("name of configuration"), - fieldWithPath(PROPERTY_CONFIGURATIONS+"[]."+ProductExecutorConfig.PROPERTY_ENABLED).description("enabled state of this config"), + fieldWithPath(PROPERTY_CONFIGURATIONS+"[]."+ProductExecutorConfig.PROPERTY_ENABLED).description("enabled state of this configuration"), fieldWithPath(PROPERTY_CONFIGURATIONS+"[]."+ProductExecutorConfig.PROPERTY_PRODUCTIDENTIFIER).description("executed product"), fieldWithPath(PROPERTY_CONFIGURATIONS+"[]."+ProductExecutorConfig.PROPERTY_EXECUTORVERSION).description("executor version"), fieldWithPath(PROPERTY_CONFIGURATIONS+"[]."+ProductExecutorConfig.PROPERTY_SETUP+"."+ProductExecutorConfigSetup.PROPERTY_BASEURL).ignored(), diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutorConfigRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutorConfigRestControllerRestDocTest.java index 277856f357..a86fe82ad8 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutorConfigRestControllerRestDocTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProductExecutorConfigRestControllerRestDocTest.java @@ -121,7 +121,7 @@ public void restdoc_admin_creates_executor_config() throws Exception { TestExecutorConfig configFromUser = new TestExecutorConfig(); configFromUser.enabled = false; - configFromUser.name = "PDS gosec config 1"; + configFromUser.name = "PDS gosec configuration 1"; configFromUser.productIdentifier = ProductIdentifier.PDS_CODESCAN.name(); configFromUser.executorVersion = 1; configFromUser.setup.baseURL = "https://productXYZ.example.com"; diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/RestDocSanityTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/RestDocSanityTest.java index e964c3c91f..44667905d3 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/RestDocSanityTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/RestDocSanityTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -import com.mercedesbenz.sechub.docgen.util.TextFileReader; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileReader; class RestDocSanityTest { @@ -18,7 +18,7 @@ void check_restdoc_tests_are_not_using_any_imports_of_com_epages() { assertTrue(file.exists()); assertTrue(file.isDirectory()); - TextFileReader reader = new TextFileReader(); + DocGenTextFileReader reader = new DocGenTextFileReader(); StringBuilder sb = new StringBuilder(); int count = 0; @@ -26,7 +26,7 @@ void check_restdoc_tests_are_not_using_any_imports_of_com_epages() { for (File sourceFile : file.listFiles((dirFile, name) -> name.endsWith("RestDocTest.java"))) { count++; // System.out.println("sourcefile:"+sourceFile); - String sourceCode = reader.loadTextFile(sourceFile); + String sourceCode = reader.readTextFromFile(sourceFile); if (sourceCode.indexOf("import static com.epages") != -1) { sb.append("- ").append(sourceFile.getName()).append(" does use static import of com.epages. Must be changed.\n"); } else if (sourceCode.indexOf("import com.epages") != -1) { diff --git a/sechub-integrationtest/pds/product-scripts/pds-solutions-gitleaks-mocked.sh b/sechub-integrationtest/pds/product-scripts/pds-solutions-gitleaks-mocked.sh index 96096aea6f..b02af0ed72 100755 --- a/sechub-integrationtest/pds/product-scripts/pds-solutions-gitleaks-mocked.sh +++ b/sechub-integrationtest/pds/product-scripts/pds-solutions-gitleaks-mocked.sh @@ -9,4 +9,18 @@ echo "PDS solutions GITLEAKS mock starting" cp "./../sechub-pds-solutions/gitleaks/docker/mocks/mock.sarif.json" "$PDS_JOB_RESULT_FILE" warnMessage "mocked result" -infoMessage "product:gitleaks" \ No newline at end of file +infoMessage "product:gitleaks" + +# Here we test the sechub wrapper secret validator application with the results of "mock.sarif.json" +export PDS_INTEGRATIONTEST_ENABLED=true +export TOOL_FOLDER=./../sechub-integrationtest/build/pds-tools + +# Export the config file necessary for the sechub wrapper secret validator application +# Besides the config file the wrapper application will automatically use the PDS_JOB_RESULT_FILE, +# which is already available in this context +export SECRET_VALIDATOR_CONFIGFILE="./../sechub-pds-solutions/gitleaks/docker/sechub-wrapper-secret-validator-config.json" + +# Uses the original gitleaks.sh script from the pds gitleaks +# Since 'PDS_INTEGRATIONTEST_ENABLED=true' gitleaks will no be executed. +# The wrapper application starts with the 'integrationtest' profile and will not perform real web reuqests +source ./../sechub-pds-solutions/gitleaks/docker/scripts/gitleaks.sh diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/SecurityTestHelper.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/SecurityTestHelper.java index 7e6e07b3d8..182188aa40 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/SecurityTestHelper.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/SecurityTestHelper.java @@ -377,7 +377,7 @@ private void ensureCipherTestDone() throws Exception { } File file = new File("./build/test-results/ciphertest/sechub-" + targetType.id + ".json"); - String text = TestFileReader.loadTextFile(file); + String text = TestFileReader.readTextFromFile(file); ObjectMapper mapper = JSONTestSupport.DEFAULT.createObjectMapper(); cipherTestData = mapper.readValue(text.getBytes(), CipherTestData.class); diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsPDSUser.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsPDSUser.java index 730e92c6a9..5a4329491d 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsPDSUser.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsPDSUser.java @@ -54,7 +54,7 @@ public String getJobStatus(UUID jobUUID) { public PDSJobStatusState getJobStatusState(UUID jobUUID) { PDSJobStatus pdsJobStatus = getJobStatusObject(jobUUID); - return pdsJobStatus.state; + return pdsJobStatus.getState(); } public PDSJobStatus getJobStatusObject(UUID jobUUID) { diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java index abf3f931a0..0a260f0274 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java @@ -44,6 +44,8 @@ import com.mercedesbenz.sechub.integrationtest.internal.TestAutoCleanupData; import com.mercedesbenz.sechub.integrationtest.internal.TestJSONHelper; import com.mercedesbenz.sechub.integrationtest.internal.TestRestHelper; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; import com.mercedesbenz.sechub.sharedkernel.project.ProjectAccessLevel; import com.mercedesbenz.sechub.test.SecHubTestURLBuilder; import com.mercedesbenz.sechub.test.TestUtil; @@ -635,7 +637,7 @@ public String getHTMLJobReport(TestProject project, UUID jobUUID) { String html = getRestHelper().getStringFromURL(getUrlBuilder().buildGetJobReportUrl(project.getProjectId(), jobUUID), MediaType.TEXT_HTML); if (enableHTMLautoDumps) { try { - getWriter().save(new File("./build/test-results/html-reports/" + jobUUID + ".html"), html, false); + getWriter().writeTextToFile(new File("./build/test-results/html-reports/" + jobUUID + ".html"), html, false); } catch (IOException e) { throw new IllegalStateException("Was not able to dump HTML data", e); } @@ -1307,4 +1309,16 @@ public String tryToCreateJobByJson(TestProject project, String sechubConfigAsStr } + public String rotateEncryption(SecHubEncryptionData data) { + + String url = getUrlBuilder().buildAdminStartsEncryptionRotation(); + return getRestHelper().postJson(url, data.toFormattedJSON()); + } + + public SecHubEncryptionStatus fetchEncryptionStatus() { + String url = getUrlBuilder().buildAdminFetchesEncryptionStatus(); + String json = getRestHelper().getJSON(url); + return SecHubEncryptionStatus.fromString(json); + } + } diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AssertEncryptionStatus.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AssertEncryptionStatus.java new file mode 100644 index 0000000000..fb805429a9 --- /dev/null +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AssertEncryptionStatus.java @@ -0,0 +1,87 @@ +package com.mercedesbenz.sechub.integrationtest.api; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; + +public class AssertEncryptionStatus { + + private SecHubEncryptionStatus status; + + private static final Logger LOG = LoggerFactory.getLogger(AssertEncryptionStatus.class); + + public static AssertEncryptionStatus assertEncryptionStatus(SecHubEncryptionStatus status) { + return new AssertEncryptionStatus(status); + } + + private AssertEncryptionStatus(SecHubEncryptionStatus status) { + if (status == null) { + throw new AssertionError("Encryption status was null!"); + } + this.status = status; + } + + public AssertDomainEncryptionStatus domain(String domainName) { + + List domains = status.getDomains(); + for (SecHubDomainEncryptionStatus domain : domains) { + if (domain.getName().equalsIgnoreCase(domainName)) { + return new AssertDomainEncryptionStatus(domain); + } + } + throw new AssertionError("Encryption status was null!"); + } + + public class AssertDomainEncryptionStatus { + + private SecHubDomainEncryptionStatus domain; + + private AssertDomainEncryptionStatus(SecHubDomainEncryptionStatus domain) { + this.domain = domain; + } + + /** + * Return to parent assertion level + * + * @return encryption status assert object + */ + public AssertEncryptionStatus encryptionStatus() { + return AssertEncryptionStatus.this; + } + + public AssertDomainEncryptionStatus hasData() { + if (domain.getData().isEmpty()) { + throw new AssertionError("No data available for domain: " + domain.getName()); + } + return this; + } + + public AssertDomainEncryptionStatus hasData(int expectedAmountOfData) { + int amount = domain.getData().size(); + + if (amount != expectedAmountOfData) { + + dump(); + + throw new AssertionError("Not expected amount of data available for domain: " + domain.getName() + ", expected was: " + expectedAmountOfData + + ", found: " + amount); + } + return this; + } + + public int getDataSize() { + return domain.getData().size(); + } + + } + + public AssertEncryptionStatus dump() { + LOG.info("Dump encrpytion status object:\n{}", status.toFormattedJSON()); + return this; + } + +} diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java index 5bcf660474..6ac1591814 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/IntegrationTestSetup.java @@ -219,6 +219,8 @@ public void evaluate() throws Throwable { LOG.error("#"); LOG.error("#########################################################################"); LOG.error("# Wasnt able to prepare scenario:{}", scenario.getName()); + LOG.error("# Reason: {}", e.getMessage()); + LOG.error("# (for more details look in unit test stack trace output)"); LOG.error("#########################################################################"); LOG.error("Last url :" + TestRestHelper.getLastUrl()); LOG.error("Last data:" + TestRestHelper.getLastData()); diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java index 0c30a3faed..25e26c37fd 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java @@ -51,6 +51,7 @@ import com.mercedesbenz.sechub.integrationtest.internal.TestJSONHelper; import com.mercedesbenz.sechub.integrationtest.internal.TestRestHelper; import com.mercedesbenz.sechub.integrationtest.internal.autoclean.TestAutoCleanJsonDeleteCount; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionStatus; import com.mercedesbenz.sechub.sharedkernel.logging.SecurityLogData; import com.mercedesbenz.sechub.sharedkernel.messaging.IntegrationTestEventHistory; import com.mercedesbenz.sechub.test.ExampleConstants; @@ -118,6 +119,14 @@ public static AssertUserJobInfo assertUserJobInfo(TestSecHubJobInfoForUserListPa return AssertUserJobInfo.assertInfo(page); } + public static AssertEncryptionStatus assertEncryptionStatus() { + return assertEncryptionStatus(as(SUPER_ADMIN).fetchEncryptionStatus()); + } + + public static AssertEncryptionStatus assertEncryptionStatus(SecHubEncryptionStatus status) { + return AssertEncryptionStatus.assertEncryptionStatus(status); + } + /** * Asserts given report json - it will try to find report elements * @@ -1255,6 +1264,18 @@ public boolean runAndReturnTrueWhenSuccesfulImpl() throws Exception { }); } + /** + * Starts cipher pool cleanup for scheduler domain directly for test scenario. + * Normally this is done by auto cleanup mechanism only, but with this method it + * is also possible to trigger the cleanup inside integration tests. + */ + public static void startScheduleCipherPoolDataCleanup() { + resetAutoCleanupDays(0); + + String url = getURLBuilder().buildIntegrationTestStartScheduleCipherPoolDataCleanup(); + getSuperAdminRestHelper().put(url); + } + /** * Will ensure complete auto cleanup inspector is reset and that auto cleanup is * set to "wantedFormerDays days" in configuration and also in every domain auto @@ -1339,6 +1360,12 @@ public static List fetchAutoCleanupInspectionDelet } + public static Long fetchScheduleEncryptionPoolIdForJob(UUID jobUUID) { + String url = getURLBuilder().buildIntegrationTestFetchScheduleEncryptionPoolIdForSecHubJob(jobUUID); + String result = getSuperAdminRestHelper().getStringFromURL(url); + return Long.valueOf(result); + } + public static FullScanData fetchFullScanData(UUID sechubJobUIUD) { String url = getURLBuilder().buildIntegrationTestFetchFullScandata(sechubJobUIUD); @@ -1573,7 +1600,7 @@ public static List fetchJobRunStatisticData(UUID execut */ public static void storeTestReport(String fileName, String reportData) { try { - writer.save(new File(testReportStorageFolder, fileName), reportData, true); + writer.writeTextToFile(new File(testReportStorageFolder, fileName), reportData, true); } catch (Exception e) { LOG.error("Was not able to store sechub test report: {}", fileName, e); } @@ -1631,4 +1658,5 @@ private static UUID ensureConfigHasUUID(TestExecutorConfig executorConfig, TestE } return executorConfig.uuid; } + } diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/WithSecHubClient.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/WithSecHubClient.java index dc3fb18867..87156fd97d 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/WithSecHubClient.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/WithSecHubClient.java @@ -288,7 +288,7 @@ public AssertAsyncResult startAsynchronScanFor(TestProject project, SecHubScanCo File file = tempFile.toFile(); TextFileWriter writer = new TextFileWriter(); - writer.save(file, configAsJson, true); + writer.writeTextToFile(file, configAsJson, true); return startAsynchronScanFor(project, environmentVariables, apiTokenStrategy, file); } catch (IOException e) { diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/MockedAdapterSetupAccess.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/MockedAdapterSetupAccess.java index 705e38b884..c29318e67e 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/MockedAdapterSetupAccess.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/MockedAdapterSetupAccess.java @@ -18,7 +18,7 @@ public class MockedAdapterSetupAccess { private Map mapIdToMockCombination = new HashMap(); private MockedAdapterSetupAccess() { - String json = TestFileReader.loadTextFile(new File("../sechub-other/mockdata/mockdata_setup.json")); + String json = TestFileReader.readTextFromFile(new File("../sechub-other/mockdata/mockdata_setup.json")); setup = TestJSONHelper.get().createFromJSON(json, MockedAdapterSetup.class); List entries = setup.getEntries(); diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/PersistentScenarioTestDataProvider.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/PersistentScenarioTestDataProvider.java index edb76065db..c31d5cf217 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/PersistentScenarioTestDataProvider.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/PersistentScenarioTestDataProvider.java @@ -7,6 +7,9 @@ import java.io.IOException; import java.util.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Provides persistent test data for integration tests - e.g. growing ids for * test scenarios. @@ -16,6 +19,8 @@ */ public class PersistentScenarioTestDataProvider { + private static final Logger LOG = LoggerFactory.getLogger(PersistentScenarioTestDataProvider.class); + private static final String BASE_SECHUB_INTEGRATIONTEST_DATA_KEY = "sechub.integrationtest.data."; private static final String SECHUB_INTEGRATIONTEST_DATA_GROWINGID = BASE_SECHUB_INTEGRATIONTEST_DATA_KEY + "growingid"; private int grow; @@ -43,25 +48,52 @@ public PersistentScenarioTestDataProvider(GrowingScenario scenario) { } private void ensurePropertyFileExists() { - file.getParentFile().mkdirs(); + LOG.trace("Ensure test scenario property file exists: {}", file); properties = new Properties(); if (file.exists()) { + LOG.trace("File exists: {}", file); - try (FileInputStream fis = new FileInputStream(file)) { - properties.load(fis); - String d = properties.getProperty(SECHUB_INTEGRATIONTEST_DATA_GROWINGID); - if (d == null) { - grow = 0; - } else { - grow = Integer.parseInt(d); + boolean loaded = false; + int tryCount = 0; + Exception lastException = null; + while (!loaded && tryCount < 5) { + tryCount++; + + LOG.trace("Start load of properties file: {} per stream. Try count:{}", file, tryCount); + try (FileInputStream fis = new FileInputStream(file)) { + properties.load(fis); + LOG.trace("Properties loaded: {}, contains: {}", file.getName(), properties); + String d = properties.getProperty(SECHUB_INTEGRATIONTEST_DATA_GROWINGID); + LOG.trace("Properties: {}, growing id value: {}", file.getName(), d); + if (d == null) { + grow = 0; + } else { + grow = Integer.parseInt(d); + } + LOG.trace("Properties: {}, grow set to: {}", file.getName(), grow); + loaded = true; + } catch (Exception e) { + lastException = e; + LOG.trace("Properties load failed, will wait shortly and retry some time", e); + try { + Thread.sleep(500); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } } - } catch (Exception e) { + } + if (!loaded) { + LOG.trace("Properties load failed, will no longer retry, but delete file: {}", file); + this.file.delete(); - throw new IllegalStateException("cannot read growid file: " + file.getAbsolutePath() + ", so deleted as fallback", e); + throw new IllegalStateException("Cannot read growid file: " + file.getAbsolutePath() + ", so deleted as fallback", lastException); } } if (!file.exists()) { + file.getParentFile().mkdirs(); + + LOG.trace("File NOT exists: {}", file); try { file.createNewFile(); } catch (IOException e) { @@ -89,6 +121,8 @@ public void increaseGrowId() { } private void store() { + LOG.trace("Try to store property file: {}", file); + File parentFolder = file.getParentFile(); if (!parentFolder.exists()) { if (!parentFolder.mkdirs()) { @@ -100,6 +134,7 @@ private void store() { } catch (IOException e) { throw new IllegalStateException("cannot store: " + file.getAbsolutePath(), e); } + LOG.trace("Stored property file: {}, content was: {}", file.getName(), properties); } public String getGrowId() { diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/SecHubClientExecutor.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/SecHubClientExecutor.java index 479215bc84..beacc15173 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/SecHubClientExecutor.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/internal/SecHubClientExecutor.java @@ -407,7 +407,7 @@ private File ensureExampleContentFoldersExist() { if (!testFile1.exists()) { try { TestFileWriter writer = new TestFileWriter(); - writer.save("class TestMeifYouCan {}", testFile1, Charset.forName("UTF-8")); + writer.writeTextToFile("class TestMeifYouCan {}", testFile1, Charset.forName("UTF-8")); } catch (IOException e) { throw new IllegalStateException("Cannot create test output!", e); } diff --git a/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json b/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json index 08ffe92609..9b4651eaf0 100644 --- a/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json +++ b/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json @@ -229,7 +229,7 @@ }, { "id" : "PDS_SOLUTION_GITLEAKS_MOCKED", "path" : "./../sechub-integrationtest/pds/product-scripts/pds-solutions-gitleaks-mocked.sh", - "scanType" : "secretSCan", + "scanType" : "secretScan", "description" : "Returns mock data, but from real product" }, { "id" : "PDS_SOLUTION_ZAP_MOCKED", diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/FileUploadSizeScenario2IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/FileUploadSizeScenario2IntTest.java index 6f49de62df..a561756004 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/FileUploadSizeScenario2IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/FileUploadSizeScenario2IntTest.java @@ -237,7 +237,7 @@ private void testErrorOrUploadDoneAsExpected(TestData data, UUID jobUUID, UnitTe assertNotNull("Downloaded filesize file may not be null!", downloadedFileSizeFile); assertTrue("Downloaded filesize file must exist!", downloadedFileSizeFile.exists()); - String fetchedSizeAsString = TestFileReader.loadTextFile(downloadedFileSizeFile); + String fetchedSizeAsString = TestFileReader.readTextFromFile(downloadedFileSizeFile); long fetchedSize = Long.parseLong(fetchedSizeAsString); assertEquals("Fetched file size not as expected for " + data.fileNameAtServerSide + " !", realFileSizeInBytes, fetchedSize); diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/JobScenario2IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/JobScenario2IntTest.java index 936ec2cc9a..45fc0530ad 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/JobScenario2IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario2/JobScenario2IntTest.java @@ -3,21 +3,30 @@ import static com.mercedesbenz.sechub.integrationtest.api.AssertMail.*; import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.*; +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.as; import static com.mercedesbenz.sechub.integrationtest.scenario2.Scenario2.*; +import static org.assertj.core.api.Assertions.*; import java.util.UUID; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.mercedesbenz.sechub.integrationtest.api.AssertJobScheduler.TestExecutionResult; import com.mercedesbenz.sechub.integrationtest.api.AssertJobScheduler.TestExecutionState; import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestMockMode; import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestSetup; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; public class JobScenario2IntTest { + private static final Logger LOG = LoggerFactory.getLogger(JobScenario2IntTest.class); + @Rule public IntegrationTestSetup setup = IntegrationTestSetup.forScenario(Scenario2.class); @@ -95,14 +104,82 @@ public void a_triggered_job_is_found_in_running_jobs_list_by_admin__when_not_alr } @Test - public void a_triggered_job_is_NOT_found_in_running_jobs_list_by_admin__when_already_done() { + public void job_list_for_done_job__and_encryption_and_cleanup_are_working() { + /* step 1 : job list entries */ + UUID doneJobUUID = assertAlreadyDoneJobIsNotListedInAdminJobList(); + int scheduleDataSizeBeforeRotate = assertEncryptionStatus().domain("schedule").hasData().getDataSize(); + + /* step 2 : rotate encryption for job */ + triggerEncryptionRotationAndAssertEncryptionIsDone(doneJobUUID); + + /* step3: check status must have now one more data */ + int scheduleDataSizeAfterRotate = assertEncryptionStatus()./* dump(). */domain("schedule").hasData().getDataSize(); + assertThat(scheduleDataSizeAfterRotate).isEqualTo(scheduleDataSizeBeforeRotate + 1); // must be one more... + + /* + * step4: rotate encryption for job again - means we ensure former job uses no + * longer the older cipher pool data and next cleanup will at least remove this + * one + */ + triggerEncryptionRotationAndAssertEncryptionIsDone(doneJobUUID); + + int scheduleDataSizeAfterRotate2 = assertEncryptionStatus()/* .dump() */.domain("schedule").hasData().getDataSize(); + assertThat(scheduleDataSizeAfterRotate2).isEqualTo(scheduleDataSizeBeforeRotate + 2); // must be one more... + + /* + * now cleanup cipher pool data (we do not want to wait for auto cleanup... + * takes too long time + */ + startScheduleCipherPoolDataCleanup(); + + /* + * wait until auto cleanup is done and encryption pool is cleaned + */ + executeRunnableAndAcceptAssertionsMaximumTimes(20, () -> { + int scheduleDataSize3 = assertEncryptionStatus()./* dump(). */domain("schedule").hasData().getDataSize(); + LOG.info("Fetched schedule encryption pool size(3): {}", scheduleDataSize3); + assertThat(scheduleDataSize3).isLessThan(scheduleDataSizeAfterRotate2); // must be less + + }, 500); + } + + private void triggerEncryptionRotationAndAssertEncryptionIsDone(UUID doneJobUUID) { + /* @formatter:off */ + /* prepare 3 */ + Long formerEncryptionPoolid = fetchScheduleEncryptionPoolIdForJob(doneJobUUID); + LOG.info("Job: {} had encryption pool id: {}", doneJobUUID, formerEncryptionPoolid); + + SecHubEncryptionData data = new SecHubEncryptionData(); + data.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_256); + data.setPasswordSourceType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + data.setPasswordSourceData("INTEGRATION_TEST_SECRET_1_AES_256"); // see IntegrationTestEncryptionEnvironmentEntryProvider + + /* execution 3 - change encryption */ + as(SUPER_ADMIN).rotateEncryption(data); + + /* test 3 */ + executeRunnableAndAcceptAssertionsMaximumTimes(10, ()->{ + + Long newEncryptionPoolid = fetchScheduleEncryptionPoolIdForJob(doneJobUUID); + assertThat(newEncryptionPoolid).isNotEqualTo(formerEncryptionPoolid); + LOG.info("Job: {} has now encryption pool id: {}", doneJobUUID, newEncryptionPoolid); + + }, 500); + /* @formatter:on */ + } + + private UUID assertAlreadyDoneJobIsNotListedInAdminJobList() { + /* @formatter:off */ + /* prepare */ as(SUPER_ADMIN).assignUserToProject(USER_1, PROJECT_1); /* @formatter:off */ + /* execute 1 - start job */ UUID jobUUID = assertUser(USER_1). canCreateWebScan(PROJECT_1,IntegrationTestMockMode.WEBSCAN__NETSPARKER_GREEN__ZERO_WAIT); + /* test 1 - start job and wait job to be done. After this not in running jobs any more*/ assertUser(USER_1). onJobScheduling(PROJECT_1). canFindJob(jobUUID). @@ -121,8 +198,8 @@ public void a_triggered_job_is_NOT_found_in_running_jobs_list_by_admin__when_alr and(). onJobAdministration(). canNotFindRunningJob(jobUUID); // means events are triggered and handled */ - /* @formatter:on */ - + return jobUUID; + /* @formatter:on */ } } diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java index 9151ba3af6..c15c848939 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java @@ -32,6 +32,8 @@ import com.mercedesbenz.sechub.commons.model.SecHubSourceDataConfiguration; import com.mercedesbenz.sechub.commons.model.SecHubStatus; import com.mercedesbenz.sechub.commons.model.SecHubWebScanConfiguration; +import com.mercedesbenz.sechub.commons.model.Severity; +import com.mercedesbenz.sechub.commons.model.TrafficLight; import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestSetup; import com.mercedesbenz.sechub.integrationtest.api.TestAPI; import com.mercedesbenz.sechub.integrationtest.api.TestProject; @@ -72,7 +74,16 @@ public void pds_solution_scancode_spdx_mocked_report_in_json_and_html_available( @Test public void pds_solution_gitleaks_mocked_report_in_json_and_html_available() throws Exception { - executePDSSolutionJobAndStoreReports(ScanType.SECRET_SCAN, PROJECT_6, "gitleaks"); + SecHubReportModel report = executePDSSolutionJobAndStoreReports(ScanType.SECRET_SCAN, PROJECT_6, "gitleaks"); + /* @formatter:off */ + assertReportUnordered(report.toJSON()) + .hasTrafficLight(TrafficLight.RED) + .finding() + .severity(Severity.CRITICAL) + .scanType(ScanType.SECRET_SCAN) + .description("github-pat has detected secret for file UnSAFE_Bank/iOS/Source Code/Pods/README.adoc.") + .isContained(); + /* @formatter:on */ } @Test @@ -100,7 +111,7 @@ public void pds_solution_findsecuritybugs_mocked_report_in_json_and_html_availab executePDSSolutionJobAndStoreReports(ScanType.CODE_SCAN, PROJECT_10, "findsecuritybugs"); } - private void executePDSSolutionJobAndStoreReports(ScanType scanType, TestProject project, String solutionName) { + private SecHubReportModel executePDSSolutionJobAndStoreReports(ScanType scanType, TestProject project, String solutionName) { SecHubConfigurationModel model = createTestModelFor(scanType, project); UUID jobUUID = as(USER_1).createJobAndReturnJobUUID(project, model); @@ -162,6 +173,7 @@ private void executePDSSolutionJobAndStoreReports(ScanType scanType, TestProject String spdxReport = as(USER_1).getSpdxReport(project, jobUUID); storeTestReport(reportName + ".spdx.json", spdxReport); } + return report; } private SecHubConfigurationModel createTestModelFor(ScanType type, TestProject project) { diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario6/DirectPDSAPICheckmarxWrapperScenario6IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario6/DirectPDSAPICheckmarxWrapperScenario6IntTest.java index fd00d31208..2892f275e8 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario6/DirectPDSAPICheckmarxWrapperScenario6IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario6/DirectPDSAPICheckmarxWrapperScenario6IntTest.java @@ -37,7 +37,7 @@ public void pds_techuser_can_start_checkmarx_scan_with_job_configuration_and_PDS /* prepare */ TestUser user = PDS_TECH_USER; - String json =TestFileReader.loadTextFile("src/test/resources/pds/checkmarx-wrapper/checkmarx-pds-job1.json"); + String json =TestFileReader.readTextFromFile("src/test/resources/pds/checkmarx-wrapper/checkmarx-pds-job1.json"); String createResult = asPDSUser(PDS_ADMIN). createJobByJsonConfiguration(json); diff --git a/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/NotificationMessageHandler.java b/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/NotificationMessageHandler.java index 7dedd91efc..411e0a50e5 100644 --- a/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/NotificationMessageHandler.java +++ b/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/NotificationMessageHandler.java @@ -10,15 +10,8 @@ import com.mercedesbenz.sechub.domain.notification.owner.InformOwnerThatProjectHasBeenDeletedNotificationService; import com.mercedesbenz.sechub.domain.notification.owner.InformThatProjectHasNewOwnerNotificationService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatJobRestartHasBeenTriggeredService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatJobRestartWasCanceledService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatJobResultsHaveBeenPurgedService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatNewSchedulerInstanceHasBeenStarted; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatProjectHasBeenDeletedNotificationService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatSchedulerJobProcessingHasBeenDisabledService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatSchedulerJobProcessingHasBeenEnabledService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatUserBecomesAdminNotificationService; -import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatUserNoLongerAdminNotificationService; +import com.mercedesbenz.sechub.domain.notification.superadmin.*; +import com.mercedesbenz.sechub.domain.notification.superadmin.InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService; import com.mercedesbenz.sechub.domain.notification.user.*; import com.mercedesbenz.sechub.sharedkernel.messaging.AsynchronMessageHandler; import com.mercedesbenz.sechub.sharedkernel.messaging.ClusterMemberMessage; @@ -102,7 +95,7 @@ public class NotificationMessageHandler implements AsynchronMessageHandler { InformUsersThatProjectHasBeenDeletedNotificationService informUsersThatProjectHasBeenDeletedService; @Autowired - InformAdminsThatNewSchedulerInstanceHasBeenStarted informAdminsThatNewSchedulerInstanceHasBeenStarted; + InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService informAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService; @Autowired SignUpRequestedUserNotificationService signupRequestedUserNotificationService; @@ -175,7 +168,7 @@ private void handleUserEmailChanged(UserMessage userMessage) { @IsReceivingAsyncMessage(MessageID.SCHEDULER_STARTED) private void handleSchedulerStarted(ClusterMemberMessage clusterMemberMessage, String baseUrl) { - informAdminsThatNewSchedulerInstanceHasBeenStarted.notify(baseUrl, clusterMemberMessage); + informAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.notify(baseUrl, clusterMemberMessage); } @IsReceivingAsyncMessage(MessageID.JOB_RESULTS_PURGED) diff --git a/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStarted.java b/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.java similarity index 94% rename from sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStarted.java rename to sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.java index 110a678ba7..8e40e082bc 100644 --- a/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStarted.java +++ b/sechub-notification/src/main/java/com/mercedesbenz/sechub/domain/notification/superadmin/InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.java @@ -17,9 +17,9 @@ import com.mercedesbenz.sechub.sharedkernel.usecases.admin.status.UseCaseAdminReceivesNotificationAboutNewchedulerInstanceStart; @Service -public class InformAdminsThatNewSchedulerInstanceHasBeenStarted { +public class InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService { - private static final Logger LOG = LoggerFactory.getLogger(InformAdminsThatNewSchedulerInstanceHasBeenStarted.class); + private static final Logger LOG = LoggerFactory.getLogger(InformAdminsThatNewSchedulerInstanceHasBeenStartedNotificationService.class); @Value("${sechub.notification.scheduler.startup.enabled:true}") @MustBeDocumented(scope = "administration", value = "When enabled, administrators will be informed by notification " diff --git a/sechub-openapi-java-client/build.gradle b/sechub-openapi-java-client/build.gradle new file mode 100644 index 0000000000..a10031ad77 --- /dev/null +++ b/sechub-openapi-java-client/build.gradle @@ -0,0 +1,132 @@ +import org.openapitools.generator.gradle.plugin.tasks.GenerateTask + +// SPDX-License-Identifier: MIT +/*============================================================================ +* Build file for subproject +* +* Root build file: "${rootProject.projectDir}/build.gradle" +* ============================================================================ +*/ + +plugins { + id 'org.openapi.generator' +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4.1' + implementation 'javax.annotation:javax.annotation-api:1.3.2' + implementation 'com.google.code.findbugs:jsr305:3.0.2' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2' + implementation 'org.openapitools:jackson-databind-nullable:0.2.6' +} + +clean.doFirst { + delete 'gen' +} + +def openApiSpecPath = "${project.rootDir}/sechub-api-java/src/main/resources/openapi.yaml" + +openApiGenerate { + generatorName = 'java' + inputSpec = openApiSpecPath + outputDir = "${projectDir}/gen" + apiPackage = 'com.mercedesbenz.sechub.api.internal.gen' + invokerPackage = 'com.mercedesbenz.sechub.api.internal.gen.invoker' + modelPackage = 'com.mercedesbenz.sechub.api.internal.gen.model' + packageName = 'com.mercedesbenz.sechub.api.internal.gen' + generateModelTests = false + generateApiTests = false + + globalProperties = [ + validateSpec : 'true', + modelDocs : 'false', + models : '', // generate all + apis : '', // generate all + supportingFiles: '', // generate all + ] + // java generator: https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/java.md + configOptions = [ + groupId : 'com.mercedesbenz.sechub', + version : "${project.version}", + performBeanValidation : 'false', + useBeanValidation : 'false', + dateLibrary : 'java8', + serializableModel : 'true', + serializationLibrary : 'jackson', + artifactId : 'sechub-openapi-java-client', + artifactDescription : 'The SecHub API Java client', + artifactUrl : 'https://github.com/mercedes-benz/sechub', + legacyDiscriminatorBehavior: 'false', + library : 'native', // Uses the Java HTTP Client (available in Java 11+) + licenseName : 'MIT', + licenseUrl : 'https://github.com/mercedes-benz/sechub/blob/develop/LICENSE', + developerEmail : '', + developerName : '', + developerOrganization : 'Mercedes-Benz Tech Innovation', + developerOrganizationUrl : 'https://www.mercedes-benz-techinnovation.com/', + scmConnection : '', + scmDeveloperConnection : '', + scmUrl : 'https://github.com/mercedes-benz/sechub', + useJakartaEe : 'false' + ] +} + +tasks.named('openApiGenerate').configure { + doFirst { + delete 'gen' + } +} + +tasks.withType(GenerateTask).configureEach { + outputs.upToDateWhen { false } + outputs.cacheIf { false } +} + +ext.apiPublishNecessary = "${project.version}" != '0.0.0' + +/* + * The assemble task will normally be called from IDE integrations (eclipse buildship, intellij) to setup parts + */ +tasks.named('assemble') { + dependsOn tasks.named('openApiGenerate') // an assemble shall always generate all parts +} + +tasks.named('compileJava') { + dependsOn tasks.named('openApiGenerate') +} + +/* +* If the gradle task eclipse is used, the assemble task shall be used as well +* to avoid compile errors. +* +* Direct IDE integrations (eclipse buildship, intellij) normally directly use +* the assemble task before importing projects/modules, so it should work there always. +*/ +tasks.named('eclipse') { + dependsOn('assemble') +} + +/** + * Custom gradle task to build a 'fatJar'. + */ +tasks.register('buildJavaApiAll', Jar) { + group 'sechub' + description 'Builds the java api library as standalone library.' + archivesBaseName = 'sechub-java-api-all' + from { + configurations.runtimeClasspath.collect { + it.isDirectory() ? it : zipTree(it) + } + } + duplicatesStrategy = DuplicatesStrategy.INCLUDE + with jar +} + +sourceSets { + main { + java { + srcDir "$rootDir/sechub-openapi-java-client/gen/src/main/java" + } + } +} \ No newline at end of file diff --git a/sechub-openapi-java/build.gradle b/sechub-openapi-java/build.gradle new file mode 100644 index 0000000000..0cf7824982 --- /dev/null +++ b/sechub-openapi-java/build.gradle @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + /*============================================================================ + * Build file for subproject + * + * Root build file: "${rootProject.projectDir}/build.gradle" + * ============================================================================ + */ diff --git a/sechub-openapi-java/src/main/resources/openapi.yaml b/sechub-openapi-java/src/main/resources/openapi.yaml index 446e5cad94..79c63fc5d8 100644 --- a/sechub-openapi-java/src/main/resources/openapi.yaml +++ b/sechub-openapi-java/src/main/resources/openapi.yaml @@ -30,6 +30,8 @@ tags: description: Operations relevant to testing - name: Configuration description: Operations relevant to configuration parts + - name: Encryption + description: Operations relevant to encryption - name: Other description: All other use cases @@ -1641,7 +1643,97 @@ components: properties: cleanupTime: $ref: '#/components/schemas/CleanupTime' - + + ################ + ## Encryption ## + ################ + SecHubCipherPasswordSourceType: + title: SecHubCipherPasswordSourceType + type: string + enum: + - NONE + - ENVIRONMENT_VARIABLE + + SecHubPasswordSource: + title: SecHubPasswordSource + type: object + properties: + type: + type: object + $ref: '#/components/schemas/SecHubCipherPasswordSourceType' + data: + type: string + + SecHubCipherAlgorithm: + title: SecHubCipherAlgorithm + type: string + enum: + - NONE + - AES_GCM_SIV_128 + - AES_GCM_SIV_256 + + SecHubDomainEncryptionData: + title: SecHubDomainEncryptionData + type: object + properties: + id: + description: Identifer for encryption configuration inside the domain + type: string + algorithm: + type: object + $ref: '#/components/schemas/SecHubCipherAlgorithm' + passwordSource: + type: object + $ref: '#/components/schemas/SecHubPasswordSource' + created: + description: Creation timestamp + type: string + format: date-time + createdFrom: + description: User id of admin who has created the configuration + type: string + usage: + description: Generic information about encryption usages inside domain + type: object + additionalProperties: true + + SecHubDomainEncryptionStatus: + title: SecHubDomainEncryptionStatus + type: object + properties: + name: + type: string + data: + type: array + items: + $ref: '#/components/schemas/SecHubDomainEncryptionData' + + SecHubEncryptionStatus: + title: SecHubEncryptionStatus + type: object + properties: + type: + type: string + domains: + type: array + items: + $ref: '#/components/schemas/SecHubDomainEncryptionStatus' + + SecHubEncryptionData: + title: SecHubEncryptionData + type: object + properties: + algorithm: + description: Algorithm to use for encryption rotation + type: object + $ref: '#/components/schemas/SecHubCipherAlgorithm' + passwordSourceType: + description: Password source type to use for algorithm + type: object + $ref: '#/components/schemas/SecHubCipherPasswordSourceType' + passwordSourceData: + description: Password source data for used type. E.g. for ENVIRONMENT_VARIABLE the name of the variable + type: string ########### ## Other ## ########### @@ -3250,6 +3342,40 @@ paths: tags: - Configuration + ################ + ## Encryption ## + ################ + /api/admin/encryption/status: + get: + summary: Admin fetches encryption status + description: "An administrator fetches encryption status from all domains where encryption is used." + operationId: adminFetchesEncryptionStatus + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/SecHubEncryptionStatus' + tags: + - Encryption + + /api/admin/encryption/rotate: + post: + summary: Admin starts encryption rotation + description: "An administrator starts encryption rotation." + operationId: adminStartsEncryptionRotation + requestBody: + content: + application/json;charset=UTF-8: + schema: + $ref: '#/components/schemas/SecHubEncryptionData' + responses: + "200": + description: "Ok" + x-accepts: application/json + tags: + - Encryption + ########### ## Other ## ########### diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/config/PDSProductParameterDefinitionTest.java b/sechub-pds-commons-core/src/test/java/com/mercedesbenz/sechub/pds/commons/core/config/PDSProductParameterDefinitionTest.java similarity index 92% rename from sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/config/PDSProductParameterDefinitionTest.java rename to sechub-pds-commons-core/src/test/java/com/mercedesbenz/sechub/pds/commons/core/config/PDSProductParameterDefinitionTest.java index da061d5d2d..99ec9e2ed3 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/config/PDSProductParameterDefinitionTest.java +++ b/sechub-pds-commons-core/src/test/java/com/mercedesbenz/sechub/pds/commons/core/config/PDSProductParameterDefinitionTest.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.pds.config; +package com.mercedesbenz.sechub.pds.commons.core.config; import static org.junit.jupiter.api.Assertions.*; @@ -9,7 +9,6 @@ import org.junit.jupiter.params.provider.ValueSource; import com.mercedesbenz.sechub.commons.model.JSONConverter; -import com.mercedesbenz.sechub.pds.commons.core.config.PDSProductParameterDefinition; class PDSProductParameterDefinitionTest { diff --git a/sechub-pds-commons-core/src/test/java/com/mercedesbenz/sechub/pds/commons/core/config/PDSServerConfigurationTest.java b/sechub-pds-commons-core/src/test/java/com/mercedesbenz/sechub/pds/commons/core/config/PDSServerConfigurationTest.java index 1b645d5f53..080648eb8c 100644 --- a/sechub-pds-commons-core/src/test/java/com/mercedesbenz/sechub/pds/commons/core/config/PDSServerConfigurationTest.java +++ b/sechub-pds-commons-core/src/test/java/com/mercedesbenz/sechub/pds/commons/core/config/PDSServerConfigurationTest.java @@ -18,7 +18,7 @@ class PDSServerConfigurationTest { void a_correct_server_configuration_file_can_be_read_as_json() throws Exception { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/config/pds-config-example1.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/config/pds-config-example1.json")); /* execute */ PDSServerConfiguration configuration = PDSServerConfiguration.fromJSON(json); @@ -49,7 +49,7 @@ void a_correct_server_configuration_file_can_be_read_as_json() throws Exception @Test void a_server_configuration_with_unknown_entries_can_be_read() throws Exception { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./src/test/resources/config/pds-config-example2-with-unknown-parts.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/config/pds-config-example2-with-unknown-parts.json")); /* execute */ PDSServerConfiguration configuration = PDSServerConfiguration.fromJSON(json); @@ -61,7 +61,8 @@ void a_server_configuration_with_unknown_entries_can_be_read() throws Exception @Test void example_configuration_from_documentation_can_be_loaded() throws Exception { /* prepare */ - String json = TestFileReader.loadTextFile(new File("./..//sechub-doc/src/docs/asciidoc/documents/pds/product_delegation_server_config_example1.json")); + String json = TestFileReader + .readTextFromFile(new File("./..//sechub-doc/src/docs/asciidoc/documents/pds/product_delegation_server_config_example1.json")); /* execute */ PDSServerConfiguration configuration = PDSServerConfiguration.fromJSON(json); diff --git a/sechub-pds-core/build.gradle b/sechub-pds-core/build.gradle index f42e39307e..0f91b842ea 100644 --- a/sechub-pds-core/build.gradle +++ b/sechub-pds-core/build.gradle @@ -19,6 +19,8 @@ dependencies { api project(':sechub-commons-core') api project(':sechub-commons-model') api project(':sechub-commons-archive') + api project(':sechub-commons-encryption') + api project(':sechub-storage-core') api project(':sechub-pds-commons-core') diff --git a/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/PDSDocumentationScopeConstants.java b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/PDSDocumentationScopeConstants.java new file mode 100644 index 0000000000..00feb7f538 --- /dev/null +++ b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/PDSDocumentationScopeConstants.java @@ -0,0 +1,6 @@ +package com.mercedesbenz.sechub.pds.usecase; + +public class PDSDocumentationScopeConstants { + + public static final String SCOPE_ENCRYPTION = "Encryption"; +} diff --git a/sechub-pds-solutions/checkmarx/10-create-image.sh b/sechub-pds-solutions/checkmarx/10-create-image.sh index 17ece1acec..95ebf2c36b 100755 --- a/sechub-pds-solutions/checkmarx/10-create-image.sh +++ b/sechub-pds-solutions/checkmarx/10-create-image.sh @@ -45,23 +45,25 @@ fi echo ">> Building \"$REGISTRY:$VERSION\"" BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" -echo ">> - Base image: $BASE_IMAGE" +echo ">> Base image: $BASE_IMAGE" if [[ -z "$BUILD_TYPE" ]] ; then - BUILD_TYPE="$DEFAULT_BUILD_TYPE" + BUILD_TYPE="$DEFAULT_BUILD_TYPE" fi BUILD_ARGS+=" --build-arg BUILD_TYPE=$BUILD_TYPE" -echo ">> - Build type: $BUILD_TYPE" +echo ">> Build type: $BUILD_TYPE" if [[ ! -z "$BUILDER_BASE_IMAGE" ]] ; then - BUILD_ARGS+=" --build-arg BUILDER_BASE_IMAGE=$BUILDER_BASE_IMAGE" - echo ">> - Builder base image: $BUILDER_BASE_IMAGE" + BUILD_ARGS+=" --build-arg BUILDER_BASE_IMAGE=$BUILDER_BASE_IMAGE" + echo ">> Builder base image: $BUILDER_BASE_IMAGE" fi -if [[ ! -z "$CHECKMARX_WRAPPER_VERSION" ]] ; then - BUILD_ARGS+=" --build-arg CHECKMARX_WRAPPER_VERSION=$CHECKMARX_WRAPPER_VERSION" - echo ">> - Checkmarx Wrapper version: $CHECKMARX_WRAPPER_VERSION" +if [[ -z "$CHECKMARX_WRAPPER_VERSION" ]] ; then + # source defaults + source ./env fi +BUILD_ARGS+=" --build-arg CHECKMARX_WRAPPER_VERSION=$CHECKMARX_WRAPPER_VERSION" +echo ">> Checkmarx Wrapper version: $CHECKMARX_WRAPPER_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/checkmarx/docker/Checkmarx-Debian.dockerfile b/sechub-pds-solutions/checkmarx/docker/Checkmarx-Debian.dockerfile index 4b57a32de9..63a03ebd3e 100644 --- a/sechub-pds-solutions/checkmarx/docker/Checkmarx-Debian.dockerfile +++ b/sechub-pds-solutions/checkmarx/docker/Checkmarx-Debian.dockerfile @@ -8,8 +8,8 @@ ARG BASE_IMAGE # Build args -ARG BUILD_TYPE -ARG CHECKMARX_WRAPPER_VERSION=1.3.0 +ARG BUILD_TYPE=download +ARG CHECKMARX_WRAPPER_VERSION # The base image of the builder ARG BUILDER_BASE_IMAGE="debian:12-slim" diff --git a/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/Chart.yaml b/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/Chart.yaml index b2e59d167f..eb1ba68642 100644 --- a/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/Chart.yaml +++ b/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-checkmarx description: SecHub PDS + Checkmarx Wrapper as Helm chart for Kubernetes - -maintainers: - - name: Jeremias Eppler - - name: Sven Dolderer - +home: https://github.com/mercedes-benz/sechub type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/templates/deployment.yaml b/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/templates/deployment.yaml index 58bccfd56c..6578902410 100644 --- a/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/templates/deployment.yaml +++ b/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/values.yaml b/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/values.yaml index dd9b529b85..da67692eb4 100644 --- a/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/values.yaml +++ b/sechub-pds-solutions/checkmarx/helm/pds-checkmarx/values.yaml @@ -26,6 +26,17 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 10 + archive: + # Limiting parameters regarding the extraction of .zip archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: size: 1Gi diff --git a/sechub-pds-solutions/findsecuritybugs/10-create-image.sh b/sechub-pds-solutions/findsecuritybugs/10-create-image.sh index cb6c6ed1e3..cd380b1adf 100755 --- a/sechub-pds-solutions/findsecuritybugs/10-create-image.sh +++ b/sechub-pds-solutions/findsecuritybugs/10-create-image.sh @@ -11,6 +11,7 @@ DEFAULT_BUILD_TYPE=build usage() { cat - < Builds a docker image of SecHub PDS with FindSecurityBugs for with tag . @@ -47,26 +48,30 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -# Enforce FINDSECURITYBUGS_SHA256SUM is defined when building custom version of find-sec-bugs -if [[ ! -z "$FINDSECURITYBUGS_VERSION" ]] ; then - echo ">> FindSecurityBugs version: $FINDSECURITYBUGS_VERSION" - BUILD_ARGS+=" --build-arg FINDSECURITYBUGS_VERSION=$FINDSECURITYBUGS_VERSION" -fi +[ -z "$BUILD_TYPE" ] && BUILD_TYPE="$DEFAULT_BUILD_TYPE" +echo ">> Build type: $BUILD_TYPE" +BUILD_ARGS+=" --build-arg BUILD_TYPE=$BUILD_TYPE" -if [[ ! -z "$SPOTBUGS_VERSION" ]] ; then - echo ">> SpotBugs version: $SPOTBUGS_VERSION" - BUILD_ARGS+=" --build-arg SPOTBUGS_VERSION=$SPOTBUGS_VERSION" +if [[ -z "$FINDSECURITYBUGS_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> FindSecurityBugs version: $FINDSECURITYBUGS_VERSION" +BUILD_ARGS+=" --build-arg FINDSECURITYBUGS_VERSION=$FINDSECURITYBUGS_VERSION" -[ -z "$BUILD_TYPE" ] && BUILD_TYPE="$DEFAULT_BUILD_TYPE" -echo ">> build type: $BUILD_TYPE" -BUILD_ARGS+=" --build-arg BUILD_TYPE=$BUILD_TYPE" +if [[ -z "$SPOTBUGS_VERSION" ]] ; then + # source defaults + source ./env +fi +echo ">> SpotBugs version: $SPOTBUGS_VERSION" +BUILD_ARGS+=" --build-arg SPOTBUGS_VERSION=$SPOTBUGS_VERSION" echo "Copying install-java scripts into the docker directory" cp -rf ../../sechub-solutions-shared/install-java/ docker/ export BUILDKIT_PROGRESS=plain export DOCKER_BUILDKIT=1 + docker build --pull --no-cache $BUILD_ARGS \ --tag "$REGISTRY:$VERSION" \ --file docker/FindSecurityBugs-Debian.dockerfile docker/ diff --git a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs.yaml b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs.yaml index 51dd3107fb..e735ca1012 100644 --- a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs.yaml +++ b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - FINDSECURITYBUGS_VERSION=${FINDSECURITYBUGS_VERSION} + - SPOTBUGS_VERSION=${SPOTBUGS_VERSION} context: docker/ dockerfile: FindSecurityBugs-Debian.dockerfile container_name: pds-findsecuritybugs diff --git a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster.yaml b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster.yaml index 48250decab..2df9041395 100644 --- a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster.yaml +++ b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - FINDSECURITYBUGS_VERSION=${FINDSECURITYBUGS_VERSION} + - SPOTBUGS_VERSION=${SPOTBUGS_VERSION} context: docker/ dockerfile: FindSecurityBugs-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster_object_storage.yaml b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster_object_storage.yaml index 6d45c2b730..8cb81ef1fc 100644 --- a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster_object_storage.yaml +++ b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_cluster_object_storage.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - FINDSECURITYBUGS_VERSION=${FINDSECURITYBUGS_VERSION} + - SPOTBUGS_VERSION=${SPOTBUGS_VERSION} context: docker/ dockerfile: FindSecurityBugs-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_external-network.yaml b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_external-network.yaml index 8f75aa7823..d07c7418c2 100644 --- a/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_external-network.yaml +++ b/sechub-pds-solutions/findsecuritybugs/docker-compose_pds_findsecuritybugs_external-network.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - FINDSECURITYBUGS_VERSION=${FINDSECURITYBUGS_VERSION} + - SPOTBUGS_VERSION=${SPOTBUGS_VERSION} context: docker/ dockerfile: FindSecurityBugs-Debian.dockerfile container_name: pds-findsecuritybugs diff --git a/sechub-pds-solutions/findsecuritybugs/docker/FindSecurityBugs-Debian.dockerfile b/sechub-pds-solutions/findsecuritybugs/docker/FindSecurityBugs-Debian.dockerfile index e98c855e89..3a2cff0ea4 100644 --- a/sechub-pds-solutions/findsecuritybugs/docker/FindSecurityBugs-Debian.dockerfile +++ b/sechub-pds-solutions/findsecuritybugs/docker/FindSecurityBugs-Debian.dockerfile @@ -5,9 +5,9 @@ ARG BASE_IMAGE # Arguments # The FindSecurityBugs version to use. See https://github.com/find-sec-bugs/find-sec-bugs/releases -ARG FINDSECURITYBUGS_VERSION="1.13.0" +ARG FINDSECURITYBUGS_VERSION # The Spotbugs version to use. See https://github.com/spotbugs/spotbugs/releases -ARG SPOTBUGS_VERSION="4.8.3" +ARG SPOTBUGS_VERSION # Build type can be "build" or "download" ARG BUILD_TYPE="build" # The base image of the builder diff --git a/sechub-pds-solutions/findsecuritybugs/docker/pds-config.json b/sechub-pds-solutions/findsecuritybugs/docker/pds-config.json index 833b298de3..3e3ca4d590 100644 --- a/sechub-pds-solutions/findsecuritybugs/docker/pds-config.json +++ b/sechub-pds-solutions/findsecuritybugs/docker/pds-config.json @@ -1,53 +1,74 @@ { - "apiVersion" : "1.0", - "serverId" : "FINDSECURITYBUGS_CLUSTER", - "products" : [ { - "id" : "PDS_FINDSECURITYBUGS", - "path" : "/pds/scripts/findsecuritybugs.sh", - "envWhitelist" : [ "HELPER_FOLDER", "TOOL_FOLDER" ], - "scanType" : "codeScan", - "description" : "Runs FindSecurityBugs. FindSecurityBugs is a static analysis tool for Java, Kotlin, Scala and Groovy.", - "parameters" : { - "optional" : [ { - "key" : "pds.config.supported.datatypes", - "default" : "binary" - }, { - "key" : "findsecuritybugs.severity", - "description" : "Limit the results to findings above a severity level of EXPERIMENTAL, LOW, MEDIUM and HIGH." - }, { - "key" : "findsecuritybugs.effort", - "description" : "The analysis effort level: MIN, LESS, DEFAULT, MORE, MAX." - }, { - "key" : "findsecuritybugs.include.bugpatterns", - "description" : "Include only the given bug patterns. A list of bug patter names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." - }, { - "key" : "findsecuritybugs.exclude.bugpatterns", - "description" : "Exclude the given bug patterns. A list of bug patter names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." - } ] + "apiVersion": "1.0", + "serverId": "FINDSECURITYBUGS_CLUSTER", + "products": [ + { + "id": "PDS_FINDSECURITYBUGS", + "path": "/pds/scripts/findsecuritybugs.sh", + "scanType": "codeScan", + "description": "Runs FindSecurityBugs. FindSecurityBugs is a static analysis tool for Java, Kotlin, Scala and Groovy.", + "parameters": { + "optional": [ + { + "key": "pds.config.supported.datatypes", + "default": "binary" + }, + { + "key": "findsecuritybugs.severity", + "description": "Limit the results to findings above a severity level of EXPERIMENTAL, LOW, MEDIUM and HIGH." + }, + { + "key": "findsecuritybugs.effort", + "description": "The analysis effort level: MIN, LESS, DEFAULT, MORE, MAX." + }, + { + "key": "findsecuritybugs.include.bugpatterns", + "description": "Include only the given bug patterns. A list of bug pattern names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." + }, + { + "key": "findsecuritybugs.exclude.bugpatterns", + "description": "Exclude the given bug patterns. A list of bug pattern names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." + } + ] + }, + "envWhitelist": [ + "HELPER_FOLDER", + "PDS_MAX_FILE_UPLOAD_BYTES", + "TOOL_FOLDER" + ] + }, + { + "id": "PDS_FINDSECURITYBUGS_MOCK", + "path": "/pds/scripts/findsecuritybugs_mock.sh", + "envWhitelist": [ + "MOCK_FOLDER" + ], + "scanType": "codeScan", + "description": "Runs FindSecurityBugs mock. It returns a fixed result file.", + "parameters": { + "optional": [ + { + "key": "pds.config.supported.datatypes", + "default": "binary" + }, + { + "key": "findsecuritybugs.severity", + "description": "Limit the results to findings above a severity level of EXPERIMENTAL, LOW, MEDIUM and HIGH." + }, + { + "key": "findsecuritybugs.effort", + "description": "The analysis effort level: MIN, LESS, DEFAULT, MORE, MAX." + }, + { + "key": "findsecuritybugs.include.bugpatterns", + "description": "Include only the given bug patterns. A list of bug pattern names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." + }, + { + "key": "findsecuritybugs.exclude.bugpatterns", + "description": "Exclude the given bug patterns. A list of bug pattern names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." + } + ] + } } - }, { - "id" : "PDS_FINDSECURITYBUGS_MOCK", - "path" : "/pds/scripts/findsecuritybugs_mock.sh", - "envWhitelist" : [ "MOCK_FOLDER" ], - "scanType" : "codeScan", - "description" : "Runs FindSecurityBugs mock. It returns a fixed result file.", - "parameters" : { - "optional" : [ { - "key" : "pds.config.supported.datatypes", - "default" : "binary" - }, { - "key" : "findsecuritybugs.severity", - "description" : "Limit the results to findings above a severity level of EXPERIMENTAL, LOW, MEDIUM and HIGH." - }, { - "key" : "findsecuritybugs.effort", - "description" : "The analysis effort level: MIN, LESS, DEFAULT, MORE, MAX." - }, { - "key" : "findsecuritybugs.include.bugpatterns", - "description" : "Include only the given bug patterns. A list of bug patter names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." - }, { - "key" : "findsecuritybugs.exclude.bugpatterns", - "description" : "Exclude the given bug patterns. A list of bug patter names can be found at: https://find-sec-bugs.github.io/bugs.htm. For example: WEAK_MESSAGE_DIGEST_MD5, SQL_INJECTION." - } ] - } - } ] -} \ No newline at end of file + ] +} diff --git a/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/Chart.yaml b/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/Chart.yaml index ed2ff2fd06..03806f54b9 100644 --- a/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/Chart.yaml +++ b/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-findsecuritybugs description: FindSecurityBugs + PDS as Helm chart for Kubernetes - -maintainers: - - name: Jeremias Eppler - - name: Sven Dolderer - +home: https://github.com/mercedes-benz/sechub type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/templates/deployment.yaml b/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/templates/deployment.yaml index b0ea2fb655..a4d8b54b3d 100644 --- a/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/templates/deployment.yaml +++ b/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/values.yaml b/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/values.yaml index bd12142f7c..d615567bf7 100644 --- a/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/values.yaml +++ b/sechub-pds-solutions/findsecuritybugs/helm/pds-findsecuritybugs/values.yaml @@ -26,6 +26,17 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 5 + archive: + # Limiting parameters regarding the extraction of .zip archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: size: "1Gi" diff --git a/sechub-pds-solutions/gitleaks/10-create-image.sh b/sechub-pds-solutions/gitleaks/10-create-image.sh index a6d45639ab..28a503eb13 100755 --- a/sechub-pds-solutions/gitleaks/10-create-image.sh +++ b/sechub-pds-solutions/gitleaks/10-create-image.sh @@ -44,10 +44,12 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -if [[ ! -z "$GITLEAKS_VERSION" ]] ; then - echo ">> Gitleaks version: $GITLEAKS_VERSION" - BUILD_ARGS="$BUILD_ARGS --build-arg GITLEAKS_VERSION=$GITLEAKS_VERSION" +if [[ -z "$GITLEAKS_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> Gitleaks version: $GITLEAKS_VERSION" +BUILD_ARGS="$BUILD_ARGS --build-arg GITLEAKS_VERSION=$GITLEAKS_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/gitleaks/docker/Gitleaks.dockerfile b/sechub-pds-solutions/gitleaks/docker/Gitleaks.dockerfile index 31ba512af9..09e7681419 100644 --- a/sechub-pds-solutions/gitleaks/docker/Gitleaks.dockerfile +++ b/sechub-pds-solutions/gitleaks/docker/Gitleaks.dockerfile @@ -24,20 +24,6 @@ RUN cd "$DOWNLOAD_FOLDER" && \ # Copy custom rule file custom-gitleaks.toml COPY custom-gitleaks.toml "$TOOL_FOLDER" -# Copy additional rules -COPY additional-rule-files/ "$DOWNLOAD_FOLDER"/additional-rule-files/ - -# Merge all the rules into one configuration file -RUN for file in "$DOWNLOAD_FOLDER"/additional-rule-files/*.toml; \ - do \ - echo "" >> "$TOOL_FOLDER"/custom-gitleaks.toml; \ - cat "$file" >> "$TOOL_FOLDER"/custom-gitleaks.toml; \ - echo "" >> "$TOOL_FOLDER"/custom-gitleaks.toml; \ - done - -# Clean up after all rules are merged into one file -RUN rm --recursive --force "$DOWNLOAD_FOLDER"/* - # Copy PDS configfile COPY pds-config.json "$PDS_FOLDER"/pds-config.json diff --git a/sechub-pds-solutions/gitleaks/docker/additional-rule-files/README.adoc b/sechub-pds-solutions/gitleaks/docker/additional-rule-files/README.adoc deleted file mode 100644 index e673fcd083..0000000000 --- a/sechub-pds-solutions/gitleaks/docker/additional-rule-files/README.adoc +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -=== Additional gitleaks rule files -Inside this directory you can put multiple rule files that specify gitleaks rule patterns. -All the rules from the `.toml` files in this directory will be appended to the `custom-gitleaks.toml` file. - -[IMPORTANT] -When creating this structure, there was a small bug inside gitleaks, -which omitted the rule identifiers inside the SARIF report when specifying a custom rule file as intended by choosing `extendDefaults=true`. -Because of this issue all default rules are inside the `custom-gitleaks.toml` with a small modification on the rule `generic-api-key`. -When the issue regarding the SARIF report is resolved, the `custom-gitleaks.toml` file will be changed to the way it is intended by Gitleaks. diff --git a/sechub-pds-solutions/gitleaks/docker/additional-rule-files/certificate-rule.toml b/sechub-pds-solutions/gitleaks/docker/additional-rule-files/certificate-rule.toml deleted file mode 100644 index f10070317d..0000000000 --- a/sechub-pds-solutions/gitleaks/docker/additional-rule-files/certificate-rule.toml +++ /dev/null @@ -1,7 +0,0 @@ -[[rules]] -id = "cryptographic-certificate" -description = "Identified a cryptographic certificate, which may compromise cryptographic security and sensitive data encryption." -regex = '''(?i)-----BEGIN[ A-Z0-9_-]{0,100}CERTIFICATE( BLOCK)?-----[\s\S-]*KEY( BLOCK)?----''' -keywords = [ - "-----begin", -] \ No newline at end of file diff --git a/sechub-pds-solutions/gitleaks/docker/additional-rule-files/kubernetes-certificate-and-client-data.toml b/sechub-pds-solutions/gitleaks/docker/additional-rule-files/kubernetes-certificate-and-client-data.toml deleted file mode 100644 index 10ca11a97e..0000000000 --- a/sechub-pds-solutions/gitleaks/docker/additional-rule-files/kubernetes-certificate-and-client-data.toml +++ /dev/null @@ -1,7 +0,0 @@ -[[rules]] -id = "kubernetes-certificate-or-client-data-base64" -description = "Detected kubernetes certificate or client data base64 encoded." -regex = '''\b((?:certificate-authority-data|client-certificate-data|client-key-data)(\s*)(:)(\s*)([-A-Za-z0-9+/]*={0,3}))\b''' -keywords = [ - "certificate-authority-data","client-certificate-data","client-key-data", -] \ No newline at end of file diff --git a/sechub-pds-solutions/gitleaks/docker/custom-gitleaks.toml b/sechub-pds-solutions/gitleaks/docker/custom-gitleaks.toml index 98cfeb8d44..286e86edf3 100644 --- a/sechub-pds-solutions/gitleaks/docker/custom-gitleaks.toml +++ b/sechub-pds-solutions/gitleaks/docker/custom-gitleaks.toml @@ -1,470 +1,39 @@ -# This file has been auto-generated. Do not edit manually. -# If you would like to contribute new rules, please use -# cmd/generate/config/main.go and follow the contributing guidelines -# at https://github.com/zricethezav/gitleaks/blob/master/CONTRIBUTING.md +# SPDX-License-Identifier: MIT -# This is the default gitleaks configuration file. -# Rules and allowlists are defined within this file. -# Rules instruct gitleaks on what should be considered a secret. -# Allowlists instruct gitleaks on what is allowed, i.e. not a secret. +# Title for the gitleaks configuration file. +title = "Gitleaks custom file" -title = "gitleaks config" - -[allowlist] -description = "global allow lists" -paths = [ - '''gitleaks.toml''', - '''(.*?)(jpg|gif|doc|docx|zip|xls|pdf|bin|svg|socket|vsidx|v2|suo|wsuo|.dll|pdb|exe)$''', - '''(go.mod|go.sum)$''', - '''gradle.lockfile''', - '''node_modules''', - '''package-lock.json''', - '''yarn.lock''', - '''pnpm-lock.yaml''', - '''Database.refactorlog''', - '''vendor''', -] - -[[rules]] -id = "adafruit-api-key" -description = "Identified a potential Adafruit API Key, which could lead to unauthorized access to Adafruit services and sensitive data exposure." -regex = '''(?i)(?:adafruit)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "adafruit", -] - -[[rules]] -id = "adobe-client-id" -description = "Detected a pattern that resembles an Adobe OAuth Web Client ID, posing a risk of compromised Adobe integrations and data breaches." -regex = '''(?i)(?:adobe)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "adobe", -] - -[[rules]] -id = "adobe-client-secret" -description = "Discovered a potential Adobe Client Secret, which, if exposed, could allow unauthorized Adobe service access and data manipulation." -regex = '''(?i)\b((p8e-)(?i)[a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "p8e-", -] - -[[rules]] -id = "age-secret-key" -description = "Discovered a potential Age encryption tool secret key, risking data decryption and unauthorized access to sensitive information." -regex = '''AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}''' -keywords = [ - "age-secret-key-1", -] - -[[rules]] -id = "airtable-api-key" -description = "Uncovered a possible Airtable API Key, potentially compromising database access and leading to data leakage or alteration." -regex = '''(?i)(?:airtable)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{17})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "airtable", -] - -[[rules]] -id = "algolia-api-key" -description = "Identified an Algolia API Key, which could result in unauthorized search operations and data exposure on Algolia-managed platforms." -regex = '''(?i)(?:algolia)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "algolia", -] - -[[rules]] -id = "alibaba-access-key-id" -description = "Detected an Alibaba Cloud AccessKey ID, posing a risk of unauthorized cloud resource access and potential data compromise." -regex = '''(?i)\b((LTAI)(?i)[a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "ltai", -] - -[[rules]] -id = "alibaba-secret-key" -description = "Discovered a potential Alibaba Cloud Secret Key, potentially allowing unauthorized operations and data access within Alibaba Cloud." -regex = '''(?i)(?:alibaba)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "alibaba", -] - -[[rules]] -id = "asana-client-id" -description = "Discovered a potential Asana Client ID, risking unauthorized access to Asana projects and sensitive task information." -regex = '''(?i)(?:asana)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "asana", -] - -[[rules]] -id = "asana-client-secret" -description = "Identified an Asana Client Secret, which could lead to compromised project management integrity and unauthorized access." -regex = '''(?i)(?:asana)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "asana", -] - -[[rules]] -id = "atlassian-api-token" -description = "Detected an Atlassian API token, posing a threat to project management and collaboration tool security and data confidentiality." -regex = '''(?i)(?:atlassian|confluence|jira)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "atlassian","confluence","jira", -] - -[[rules]] -id = "authress-service-client-access-key" -description = "Uncovered a possible Authress Service Client Access Key, which may compromise access control services and sensitive data." -regex = '''(?i)\b((?:sc|ext|scauth|authress)_[a-z0-9]{5,30}\.[a-z0-9]{4,6}\.acc[_-][a-z0-9-]{10,32}\.[a-z0-9+/_=-]{30,120})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "sc_","ext_","scauth_","authress_", -] - -[[rules]] -id = "aws-access-token" -description = "Identified a pattern that may indicate AWS credentials, risking unauthorized cloud resource access and data breaches on AWS platforms." -regex = '''(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}''' -keywords = [ - "akia","asia","abia","acca", -] - -[[rules]] -id = "beamer-api-token" -description = "Detected a Beamer API token, potentially compromising content management and exposing sensitive notifications and updates." -regex = '''(?i)(?:beamer)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(b_[a-z0-9=_\-]{44})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "beamer", -] - -[[rules]] -id = "bitbucket-client-id" -description = "Discovered a potential Bitbucket Client ID, risking unauthorized repository access and potential codebase exposure." -regex = '''(?i)(?:bitbucket)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "bitbucket", -] - -[[rules]] -id = "bitbucket-client-secret" -description = "Discovered a potential Bitbucket Client Secret, posing a risk of compromised code repositories and unauthorized access." -regex = '''(?i)(?:bitbucket)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "bitbucket", -] - -[[rules]] -id = "bittrex-access-key" -description = "Identified a Bittrex Access Key, which could lead to unauthorized access to cryptocurrency trading accounts and financial loss." -regex = '''(?i)(?:bittrex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "bittrex", -] - -[[rules]] -id = "bittrex-secret-key" -description = "Detected a Bittrex Secret Key, potentially compromising cryptocurrency transactions and financial security." -regex = '''(?i)(?:bittrex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "bittrex", -] - -[[rules]] -id = "clojars-api-token" -description = "Uncovered a possible Clojars API token, risking unauthorized access to Clojure libraries and potential code manipulation." -regex = '''(?i)(CLOJARS_)[a-z0-9]{60}''' -keywords = [ - "clojars", -] - -[[rules]] -id = "codecov-access-token" -description = "Found a pattern resembling a Codecov Access Token, posing a risk of unauthorized access to code coverage reports and sensitive data." -regex = '''(?i)(?:codecov)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "codecov", -] - -[[rules]] -id = "coinbase-access-token" -description = "Detected a Coinbase Access Token, posing a risk of unauthorized access to cryptocurrency accounts and financial transactions." -regex = '''(?i)(?:coinbase)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "coinbase", -] - -[[rules]] -id = "confluent-access-token" -description = "Identified a Confluent Access Token, which could compromise access to streaming data platforms and sensitive data flow." -regex = '''(?i)(?:confluent)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "confluent", -] - -[[rules]] -id = "confluent-secret-key" -description = "Found a Confluent Secret Key, potentially risking unauthorized operations and data access within Confluent services." -regex = '''(?i)(?:confluent)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "confluent", -] - -[[rules]] -id = "contentful-delivery-api-token" -description = "Discovered a Contentful delivery API token, posing a risk to content management systems and data integrity." -regex = '''(?i)(?:contentful)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{43})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "contentful", -] - -[[rules]] -id = "databricks-api-token" -description = "Uncovered a Databricks API token, which may compromise big data analytics platforms and sensitive data processing." -regex = '''(?i)\b(dapi[a-h0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "dapi", -] - -[[rules]] -id = "datadog-access-token" -description = "Detected a Datadog Access Token, potentially risking monitoring and analytics data exposure and manipulation." -regex = '''(?i)(?:datadog)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "datadog", -] - -[[rules]] -id = "defined-networking-api-token" -description = "Identified a Defined Networking API token, which could lead to unauthorized network operations and data breaches." -regex = '''(?i)(?:dnkey)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(dnkey-[a-z0-9=_\-]{26}-[a-z0-9=_\-]{52})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "dnkey", -] - -[[rules]] -id = "digitalocean-access-token" -description = "Found a DigitalOcean OAuth Access Token, risking unauthorized cloud resource access and data compromise." -regex = '''(?i)\b(doo_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "doo_v1_", -] - -[[rules]] -id = "digitalocean-pat" -description = "Discovered a DigitalOcean Personal Access Token, posing a threat to cloud infrastructure security and data privacy." -regex = '''(?i)\b(dop_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "dop_v1_", -] - -[[rules]] -id = "digitalocean-refresh-token" -description = "Uncovered a DigitalOcean OAuth Refresh Token, which could allow prolonged unauthorized access and resource manipulation." -regex = '''(?i)\b(dor_v1_[a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "dor_v1_", -] - -[[rules]] -id = "discord-api-token" -description = "Detected a Discord API key, potentially compromising communication channels and user data privacy on Discord." -regex = '''(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "discord", -] - -[[rules]] -id = "discord-client-id" -description = "Identified a Discord client ID, which may lead to unauthorized integrations and data exposure in Discord applications." -regex = '''(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9]{18})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "discord", -] - -[[rules]] -id = "discord-client-secret" -description = "Discovered a potential Discord client secret, risking compromised Discord bot integrations and data leaks." -regex = '''(?i)(?:discord)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "discord", -] - -[[rules]] -id = "doppler-api-token" -description = "Discovered a Doppler API token, posing a risk to environment and secrets management security." -regex = '''(dp\.pt\.)(?i)[a-z0-9]{43}''' -keywords = [ - "doppler", -] - -[[rules]] -id = "droneci-access-token" -description = "Detected a Droneci Access Token, potentially compromising continuous integration and deployment workflows." -regex = '''(?i)(?:droneci)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "droneci", -] - -[[rules]] -id = "dropbox-api-token" -description = "Identified a Dropbox API secret, which could lead to unauthorized file access and data breaches in Dropbox storage." -regex = '''(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{15})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "dropbox", -] - -[[rules]] -id = "dropbox-long-lived-api-token" -description = "Found a Dropbox long-lived API token, risking prolonged unauthorized access to cloud storage and sensitive data." -regex = '''(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\-_=]{43})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "dropbox", -] - -[[rules]] -id = "dropbox-short-lived-api-token" -description = "Discovered a Dropbox short-lived API token, posing a risk of temporary but potentially harmful data access and manipulation." -regex = '''(?i)(?:dropbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(sl\.[a-z0-9\-=_]{135})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "dropbox", -] - -[[rules]] -id = "duffel-api-token" -description = "Uncovered a Duffel API token, which may compromise travel platform integrations and sensitive customer data." -regex = '''duffel_(test|live)_(?i)[a-z0-9_\-=]{43}''' -keywords = [ - "duffel", -] - -[[rules]] -id = "dynatrace-api-token" -description = "Detected a Dynatrace API token, potentially risking application performance monitoring and data exposure." -regex = '''dt0c01\.(?i)[a-z0-9]{24}\.[a-z0-9]{64}''' -keywords = [ - "dynatrace", -] - -[[rules]] -id = "easypost-api-token" -description = "Identified an EasyPost API token, which could lead to unauthorized postal and shipment service access and data exposure." -regex = '''\bEZAK(?i)[a-z0-9]{54}''' -keywords = [ - "ezak", -] - -[[rules]] -id = "easypost-test-api-token" -description = "Detected an EasyPost test API token, risking exposure of test environments and potentially sensitive shipment data." -regex = '''\bEZTK(?i)[a-z0-9]{54}''' -keywords = [ - "eztk", -] - -[[rules]] -id = "etsy-access-token" -description = "Found an Etsy Access Token, potentially compromising Etsy shop management and customer data." -regex = '''(?i)(?:etsy)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "etsy", -] - -[[rules]] -id = "facebook" -description = "Discovered a Facebook Access Token, posing a risk of unauthorized access to Facebook accounts and personal data exposure." -regex = '''(?i)(?:facebook)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "facebook", -] - -[[rules]] -id = "fastly-api-token" -description = "Uncovered a Fastly API key, which may compromise CDN and edge cloud services, leading to content delivery and security issues." -regex = '''(?i)(?:fastly)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "fastly", -] - -[[rules]] -id = "finicity-api-token" -description = "Detected a Finicity API token, potentially risking financial data access and unauthorized financial operations." -regex = '''(?i)(?:finicity)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "finicity", -] - -[[rules]] -id = "finicity-client-secret" -description = "Identified a Finicity Client Secret, which could lead to compromised financial service integrations and data breaches." -regex = '''(?i)(?:finicity)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "finicity", -] - -[[rules]] -id = "finnhub-access-token" -description = "Found a Finnhub Access Token, risking unauthorized access to financial market data and analytics." -regex = '''(?i)(?:finnhub)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "finnhub", -] - -[[rules]] -id = "flickr-access-token" -description = "Discovered a Flickr Access Token, posing a risk of unauthorized photo management and potential data leakage." -regex = '''(?i)(?:flickr)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "flickr", -] - -[[rules]] -id = "flutterwave-encryption-key" -description = "Uncovered a Flutterwave Encryption Key, which may compromise payment processing and sensitive financial information." -regex = '''FLWSECK_TEST-(?i)[a-h0-9]{12}''' -keywords = [ - "flwseck_test", -] +# Extend the base (this) configuration. When you extend a configuration +# the base rules take precedence over the extended rules. I.e., if there are +# duplicate rules in both the base configuration and the extended configuration +# the base rules will override the extended rules. +# Another thing to know with extending configurations is you can chain together +# multiple configuration files to a depth of 2. Allowlist arrays are appended +# and can contain duplicates. +# useDefault and path can NOT be used at the same time. Choose one. +[extend] +# useDefault will extend the base configuration with the default gitleaks config: +# https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml +useDefault = true [[rules]] -id = "flutterwave-public-key" -description = "Detected a Finicity Public Key, potentially exposing public cryptographic operations and integrations." -regex = '''FLWPUBK_TEST-(?i)[a-h0-9]{32}-X''' +id = "cryptographic-certificate" +description = "Identified a cryptographic certificate, which may compromise cryptographic security and sensitive data encryption." +regex = '''(?i)-----BEGIN[ A-Z0-9_-]{0,100}CERTIFICATE( BLOCK)?-----[\s\S-]*KEY( BLOCK)?----''' keywords = [ - "flwpubk_test", -] - -[[rules]] -id = "flutterwave-secret-key" -description = "Identified a Flutterwave Secret Key, risking unauthorized financial transactions and data breaches." -regex = '''FLWSECK_TEST-(?i)[a-h0-9]{32}-X''' -keywords = [ - "flwseck_test", -] - -[[rules]] -id = "frameio-api-token" -description = "Found a Frame.io API token, potentially compromising video collaboration and project management." -regex = '''fio-u-(?i)[a-z0-9\-_=]{64}''' -keywords = [ - "fio-u-", -] - -[[rules]] -id = "freshbooks-access-token" -description = "Discovered a Freshbooks Access Token, posing a risk to accounting software access and sensitive financial data exposure." -regex = '''(?i)(?:freshbooks)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "freshbooks", + "-----begin", ] [[rules]] -id = "gcp-api-key" -description = "Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches." -regex = '''(?i)\b(AIza[0-9A-Za-z\\-_]{35})(?:['|\"|\n|\r|\s|\x60|;]|$)''' +id = "kubernetes-certificate-or-client-data-base64" +description = "Detected kubernetes certificate or client data base64 encoded." +regex = '''\b((?:certificate-authority-data|client-certificate-data|client-key-data)(\s*)(:)(\s*)([-A-Za-z0-9+/]*={0,3}))\b''' keywords = [ - "aiza", + "certificate-authority-data","client-certificate-data","client-key-data", ] +# Updated generic-api-key rule with keyword 'client' removed for less false-positives +# This will overwrite the default rule with the 'id = generic-api-key' [[rules]] id = "generic-api-key" description = "Detected a Generic API Key, potentially exposing access to various services and sensitive operations." @@ -1951,897 +1520,3 @@ stopwords = [ "zsh.", "zsh_", ] - -[[rules]] -id = "github-app-token" -description = "Identified a GitHub App Token, which may compromise GitHub application integrations and source code security." -regex = '''(ghu|ghs)_[0-9a-zA-Z]{36}''' -keywords = [ - "ghu_","ghs_", -] - -[[rules]] -id = "github-fine-grained-pat" -description = "Found a GitHub Fine-Grained Personal Access Token, risking unauthorized repository access and code manipulation." -regex = '''github_pat_[0-9a-zA-Z_]{82}''' -keywords = [ - "github_pat_", -] - -[[rules]] -id = "github-oauth" -description = "Discovered a GitHub OAuth Access Token, posing a risk of compromised GitHub account integrations and data leaks." -regex = '''gho_[0-9a-zA-Z]{36}''' -keywords = [ - "gho_", -] - -[[rules]] -id = "github-pat" -description = "Uncovered a GitHub Personal Access Token, potentially leading to unauthorized repository access and sensitive content exposure." -regex = '''ghp_[0-9a-zA-Z]{36}''' -keywords = [ - "ghp_", -] - -[[rules]] -id = "github-refresh-token" -description = "Detected a GitHub Refresh Token, which could allow prolonged unauthorized access to GitHub services." -regex = '''ghr_[0-9a-zA-Z]{36}''' -keywords = [ - "ghr_", -] - -[[rules]] -id = "gitlab-pat" -description = "Identified a GitLab Personal Access Token, risking unauthorized access to GitLab repositories and codebase exposure." -regex = '''glpat-[0-9a-zA-Z\-\_]{20}''' -keywords = [ - "glpat-", -] - -[[rules]] -id = "gitlab-ptt" -description = "Found a GitLab Pipeline Trigger Token, potentially compromising continuous integration workflows and project security." -regex = '''glptt-[0-9a-f]{40}''' -keywords = [ - "glptt-", -] - -[[rules]] -id = "gitlab-rrt" -description = "Discovered a GitLab Runner Registration Token, posing a risk to CI/CD pipeline integrity and unauthorized access." -regex = '''GR1348941[0-9a-zA-Z\-\_]{20}''' -keywords = [ - "gr1348941", -] - -[[rules]] -id = "gitter-access-token" -description = "Uncovered a Gitter Access Token, which may lead to unauthorized access to chat and communication services." -regex = '''(?i)(?:gitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "gitter", -] - -[[rules]] -id = "gocardless-api-token" -description = "Detected a GoCardless API token, potentially risking unauthorized direct debit payment operations and financial data exposure." -regex = '''(?i)(?:gocardless)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(live_(?i)[a-z0-9\-_=]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "live_","gocardless", -] - -[[rules]] -id = "grafana-api-key" -description = "Identified a Grafana API key, which could compromise monitoring dashboards and sensitive data analytics." -regex = '''(?i)\b(eyJrIjoi[A-Za-z0-9]{70,400}={0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "eyjrijoi", -] - -[[rules]] -id = "grafana-cloud-api-token" -description = "Found a Grafana cloud API token, risking unauthorized access to cloud-based monitoring services and data exposure." -regex = '''(?i)\b(glc_[A-Za-z0-9+/]{32,400}={0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "glc_", -] - -[[rules]] -id = "grafana-service-account-token" -description = "Discovered a Grafana service account token, posing a risk of compromised monitoring services and data integrity." -regex = '''(?i)\b(glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "glsa_", -] - -[[rules]] -id = "hashicorp-tf-api-token" -description = "Uncovered a HashiCorp Terraform user/org API token, which may lead to unauthorized infrastructure management and security breaches." -regex = '''(?i)[a-z0-9]{14}\.atlasv1\.[a-z0-9\-_=]{60,70}''' -keywords = [ - "atlasv1", -] - -[[rules]] -id = "hashicorp-tf-password" -description = "Identified a HashiCorp Terraform password field, risking unauthorized infrastructure configuration and security breaches." -regex = '''(?i)(?:administrator_login_password|password)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}("[a-z0-9=_\-]{8,20}")(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "administrator_login_password","password", -] - -[[rules]] -id = "heroku-api-key" -description = "Detected a Heroku API Key, potentially compromising cloud application deployments and operational security." -regex = '''(?i)(?:heroku)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "heroku", -] - -[[rules]] -id = "hubspot-api-key" -description = "Found a HubSpot API Token, posing a risk to CRM data integrity and unauthorized marketing operations." -regex = '''(?i)(?:hubspot)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "hubspot", -] - -[[rules]] -id = "huggingface-access-token" -description = "Discovered a Hugging Face Access token, which could lead to unauthorized access to AI models and sensitive data." -regex = '''(?:^|[\\'"` >=:])(hf_[a-zA-Z]{34})(?:$|[\\'"` <])''' -entropy = 1 -keywords = [ - "hf_", -] - -[[rules]] -id = "huggingface-organization-api-token" -description = "Uncovered a Hugging Face Organization API token, potentially compromising AI organization accounts and associated data." -regex = '''(?:^|[\\'"` >=:\(,)])(api_org_[a-zA-Z]{34})(?:$|[\\'"` <\),])''' -entropy = 2 -keywords = [ - "api_org_", -] - -[[rules]] -id = "infracost-api-token" -description = "Detected an Infracost API Token, risking unauthorized access to cloud cost estimation tools and financial data." -regex = '''(?i)\b(ico-[a-zA-Z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "ico-", -] - -[[rules]] -id = "intercom-api-key" -description = "Identified an Intercom API Token, which could compromise customer communication channels and data privacy." -regex = '''(?i)(?:intercom)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{60})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "intercom", -] - -[[rules]] -id = "jfrog-api-key" -description = "Found a JFrog API Key, posing a risk of unauthorized access to software artifact repositories and build pipelines." -regex = '''(?i)(?:jfrog|artifactory|bintray|xray)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{73})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "jfrog","artifactory","bintray","xray", -] - -[[rules]] -id = "jfrog-identity-token" -description = "Discovered a JFrog Identity Token, potentially compromising access to JFrog services and sensitive software artifacts." -regex = '''(?i)(?:jfrog|artifactory|bintray|xray)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "jfrog","artifactory","bintray","xray", -] - -[[rules]] -id = "jwt" -description = "Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data." -regex = '''\b(ey[a-zA-Z0-9]{17,}\.ey[a-zA-Z0-9\/\\_-]{17,}\.(?:[a-zA-Z0-9\/\\_-]{10,}={0,2})?)(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "ey", -] - -[[rules]] -id = "jwt-base64" -description = "Detected a Base64-encoded JSON Web Token, posing a risk of exposing encoded authentication and data exchange information." -regex = '''\bZXlK(?:(?PaGJHY2lPaU)|(?PaGNIVWlPaU)|(?PaGNIWWlPaU)|(?PaGRXUWlPaU)|(?PaU5qUWlP)|(?PamNtbDBJanBi)|(?PamRIa2lPaU)|(?PbGNHc2lPbn)|(?PbGJtTWlPaU)|(?PcWEzVWlPaU)|(?PcWQyc2lPb)|(?PcGMzTWlPaU)|(?PcGRpSTZJ)|(?PcmFXUWlP)|(?PclpYbGZiM0J6SWpwY)|(?PcmRIa2lPaUp)|(?PdWIyNWpaU0k2)|(?Pd01tTWlP)|(?Pd01uTWlPaU)|(?Pd2NIUWlPaU)|(?PemRXSWlPaU)|(?PemRuUWlP)|(?PMFlXY2lPaU)|(?PMGVYQWlPaUp)|(?PMWNtd2l)|(?PMWMyVWlPaUp)|(?PMlpYSWlPaU)|(?PMlpYSnphVzl1SWpv)|(?PNElqb2)|(?PNE5XTWlP)|(?PNE5YUWlPaU)|(?PNE5YUWpVekkxTmlJNkl)|(?PNE5YVWlPaU)|(?PNmFYQWlPaU))[a-zA-Z0-9\/\\_+\-\r\n]{40,}={0,2}''' -keywords = [ - "zxlk", -] - -[[rules]] -id = "kraken-access-token" -description = "Identified a Kraken Access Token, potentially compromising cryptocurrency trading accounts and financial security." -regex = '''(?i)(?:kraken)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9\/=_\+\-]{80,90})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "kraken", -] - -[[rules]] -id = "kucoin-access-token" -description = "Found a Kucoin Access Token, risking unauthorized access to cryptocurrency exchange services and transactions." -regex = '''(?i)(?:kucoin)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "kucoin", -] - -[[rules]] -id = "kucoin-secret-key" -description = "Discovered a Kucoin Secret Key, which could lead to compromised cryptocurrency operations and financial data breaches." -regex = '''(?i)(?:kucoin)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "kucoin", -] - -[[rules]] -id = "launchdarkly-access-token" -description = "Uncovered a Launchdarkly Access Token, potentially compromising feature flag management and application functionality." -regex = '''(?i)(?:launchdarkly)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "launchdarkly", -] - -[[rules]] -id = "linear-api-key" -description = "Detected a Linear API Token, posing a risk to project management tools and sensitive task data." -regex = '''lin_api_(?i)[a-z0-9]{40}''' -keywords = [ - "lin_api_", -] - -[[rules]] -id = "linear-client-secret" -description = "Identified a Linear Client Secret, which may compromise secure integrations and sensitive project management data." -regex = '''(?i)(?:linear)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "linear", -] - -[[rules]] -id = "linkedin-client-id" -description = "Found a LinkedIn Client ID, risking unauthorized access to LinkedIn integrations and professional data exposure." -regex = '''(?i)(?:linkedin|linked-in)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{14})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "linkedin","linked-in", -] - -[[rules]] -id = "linkedin-client-secret" -description = "Discovered a LinkedIn Client secret, potentially compromising LinkedIn application integrations and user data." -regex = '''(?i)(?:linkedin|linked-in)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "linkedin","linked-in", -] - -[[rules]] -id = "lob-api-key" -description = "Uncovered a Lob API Key, which could lead to unauthorized access to mailing and address verification services." -regex = '''(?i)(?:lob)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}((live|test)_[a-f0-9]{35})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "test_","live_", -] - -[[rules]] -id = "lob-pub-api-key" -description = "Detected a Lob Publishable API Key, posing a risk of exposing mail and print service integrations." -regex = '''(?i)(?:lob)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}((test|live)_pub_[a-f0-9]{31})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "test_pub","live_pub","_pub", -] - -[[rules]] -id = "mailchimp-api-key" -description = "Identified a Mailchimp API key, potentially compromising email marketing campaigns and subscriber data." -regex = '''(?i)(?:mailchimp)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{32}-us20)(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "mailchimp", -] - -[[rules]] -id = "mailgun-private-api-token" -description = "Found a Mailgun private API token, risking unauthorized email service operations and data breaches." -regex = '''(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(key-[a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "mailgun", -] - -[[rules]] -id = "mailgun-pub-key" -description = "Discovered a Mailgun public validation key, which could expose email verification processes and associated data." -regex = '''(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(pubkey-[a-f0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "mailgun", -] - -[[rules]] -id = "mailgun-signing-key" -description = "Uncovered a Mailgun webhook signing key, potentially compromising email automation and data integrity." -regex = '''(?i)(?:mailgun)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "mailgun", -] - -[[rules]] -id = "mapbox-api-token" -description = "Detected a MapBox API token, posing a risk to geospatial services and sensitive location data exposure." -regex = '''(?i)(?:mapbox)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(pk\.[a-z0-9]{60}\.[a-z0-9]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "mapbox", -] - -[[rules]] -id = "mattermost-access-token" -description = "Identified a Mattermost Access Token, which may compromise team communication channels and data privacy." -regex = '''(?i)(?:mattermost)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{26})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "mattermost", -] - -[[rules]] -id = "messagebird-api-token" -description = "Found a MessageBird API token, risking unauthorized access to communication platforms and message data." -regex = '''(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{25})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "messagebird","message-bird","message_bird", -] - -[[rules]] -id = "messagebird-client-id" -description = "Discovered a MessageBird client ID, potentially compromising API integrations and sensitive communication data." -regex = '''(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "messagebird","message-bird","message_bird", -] - -[[rules]] -id = "microsoft-teams-webhook" -description = "Uncovered a Microsoft Teams Webhook, which could lead to unauthorized access to team collaboration tools and data leaks." -regex = '''https:\/\/[a-z0-9]+\.webhook\.office\.com\/webhookb2\/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}@[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}\/IncomingWebhook\/[a-z0-9]{32}\/[a-z0-9]{8}-([a-z0-9]{4}-){3}[a-z0-9]{12}''' -keywords = [ - "webhook.office.com","webhookb2","incomingwebhook", -] - -[[rules]] -id = "netlify-access-token" -description = "Detected a Netlify Access Token, potentially compromising web hosting services and site management." -regex = '''(?i)(?:netlify)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{40,46})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "netlify", -] - -[[rules]] -id = "new-relic-browser-api-token" -description = "Identified a New Relic ingest browser API token, risking unauthorized access to application performance data and analytics." -regex = '''(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(NRJS-[a-f0-9]{19})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "nrjs-", -] - -[[rules]] -id = "new-relic-user-api-id" -description = "Found a New Relic user API ID, posing a risk to application monitoring services and data integrity." -regex = '''(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "new-relic","newrelic","new_relic", -] - -[[rules]] -id = "new-relic-user-api-key" -description = "Discovered a New Relic user API Key, which could lead to compromised application insights and performance monitoring." -regex = '''(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(NRAK-[a-z0-9]{27})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "nrak", -] - -[[rules]] -id = "npm-access-token" -description = "Uncovered an npm access token, potentially compromising package management and code repository access." -regex = '''(?i)\b(npm_[a-z0-9]{36})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "npm_", -] - -[[rules]] -id = "nytimes-access-token" -description = "Detected a Nytimes Access Token, risking unauthorized access to New York Times APIs and content services." -regex = '''(?i)(?:nytimes|new-york-times,|newyorktimes)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "nytimes","new-york-times","newyorktimes", -] - -[[rules]] -id = "okta-access-token" -description = "Identified an Okta Access Token, which may compromise identity management services and user authentication data." -regex = '''(?i)(?:okta)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9=_\-]{42})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "okta", -] - -[[rules]] -id = "openai-api-key" -description = "Found an OpenAI API Key, posing a risk of unauthorized access to AI services and data manipulation." -regex = '''(?i)\b(sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "t3blbkfj", -] - -[[rules]] -id = "plaid-api-token" -description = "Discovered a Plaid API Token, potentially compromising financial data aggregation and banking services." -regex = '''(?i)(?:plaid)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(access-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "plaid", -] - -[[rules]] -id = "plaid-client-id" -description = "Uncovered a Plaid Client ID, which could lead to unauthorized financial service integrations and data breaches." -regex = '''(?i)(?:plaid)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{24})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -entropy = 3.5 -keywords = [ - "plaid", -] - -[[rules]] -id = "plaid-secret-key" -description = "Detected a Plaid Secret key, risking unauthorized access to financial accounts and sensitive transaction data." -regex = '''(?i)(?:plaid)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -entropy = 3.5 -keywords = [ - "plaid", -] - -[[rules]] -id = "planetscale-api-token" -description = "Identified a PlanetScale API token, potentially compromising database management and operations." -regex = '''(?i)\b(pscale_tkn_(?i)[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "pscale_tkn_", -] - -[[rules]] -id = "planetscale-oauth-token" -description = "Found a PlanetScale OAuth token, posing a risk to database access control and sensitive data integrity." -regex = '''(?i)\b(pscale_oauth_(?i)[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "pscale_oauth_", -] - -[[rules]] -id = "planetscale-password" -description = "Discovered a PlanetScale password, which could lead to unauthorized database operations and data breaches." -regex = '''(?i)\b(pscale_pw_(?i)[a-z0-9=\-_\.]{32,64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "pscale_pw_", -] - -[[rules]] -id = "postman-api-token" -description = "Uncovered a Postman API token, potentially compromising API testing and development workflows." -regex = '''(?i)\b(PMAK-(?i)[a-f0-9]{24}\-[a-f0-9]{34})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "pmak-", -] - -[[rules]] -id = "prefect-api-token" -description = "Detected a Prefect API token, risking unauthorized access to workflow management and automation services." -regex = '''(?i)\b(pnu_[a-z0-9]{36})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "pnu_", -] - -[[rules]] -id = "private-key" -description = "Identified a Private Key, which may compromise cryptographic security and sensitive data encryption." -regex = '''(?i)-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY( BLOCK)?-----[\s\S-]*KEY( BLOCK)?----''' -keywords = [ - "-----begin", -] - -[[rules]] -id = "pulumi-api-token" -description = "Found a Pulumi API token, posing a risk to infrastructure as code services and cloud resource management." -regex = '''(?i)\b(pul-[a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "pul-", -] - -[[rules]] -id = "pypi-upload-token" -description = "Discovered a PyPI upload token, potentially compromising Python package distribution and repository integrity." -regex = '''pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\-_]{50,1000}''' -keywords = [ - "pypi-ageichlwas5vcmc", -] - -[[rules]] -id = "rapidapi-access-token" -description = "Uncovered a RapidAPI Access Token, which could lead to unauthorized access to various APIs and data services." -regex = '''(?i)(?:rapidapi)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9_-]{50})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "rapidapi", -] - -[[rules]] -id = "readme-api-token" -description = "Detected a Readme API token, risking unauthorized documentation management and content exposure." -regex = '''(?i)\b(rdme_[a-z0-9]{70})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "rdme_", -] - -[[rules]] -id = "rubygems-api-token" -description = "Identified a Rubygem API token, potentially compromising Ruby library distribution and package management." -regex = '''(?i)\b(rubygems_[a-f0-9]{48})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "rubygems_", -] - -[[rules]] -id = "scalingo-api-token" -description = "Found a Scalingo API token, posing a risk to cloud platform services and application deployment security." -regex = '''\btk-us-[a-zA-Z0-9-_]{48}\b''' -keywords = [ - "tk-us-", -] - -[[rules]] -id = "sendbird-access-id" -description = "Discovered a Sendbird Access ID, which could compromise chat and messaging platform integrations." -regex = '''(?i)(?:sendbird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "sendbird", -] - -[[rules]] -id = "sendbird-access-token" -description = "Uncovered a Sendbird Access Token, potentially risking unauthorized access to communication services and user data." -regex = '''(?i)(?:sendbird)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "sendbird", -] - -[[rules]] -id = "sendgrid-api-token" -description = "Detected a SendGrid API token, posing a risk of unauthorized email service operations and data exposure." -regex = '''(?i)\b(SG\.(?i)[a-z0-9=_\-\.]{66})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "sg.", -] - -[[rules]] -id = "sendinblue-api-token" -description = "Identified a Sendinblue API token, which may compromise email marketing services and subscriber data privacy." -regex = '''(?i)\b(xkeysib-[a-f0-9]{64}\-(?i)[a-z0-9]{16})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "xkeysib-", -] - -[[rules]] -id = "sentry-access-token" -description = "Found a Sentry Access Token, risking unauthorized access to error tracking services and sensitive application data." -regex = '''(?i)(?:sentry)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "sentry", -] - -[[rules]] -id = "shippo-api-token" -description = "Discovered a Shippo API token, potentially compromising shipping services and customer order data." -regex = '''(?i)\b(shippo_(live|test)_[a-f0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "shippo_", -] - -[[rules]] -id = "shopify-access-token" -description = "Uncovered a Shopify access token, which could lead to unauthorized e-commerce platform access and data breaches." -regex = '''shpat_[a-fA-F0-9]{32}''' -keywords = [ - "shpat_", -] - -[[rules]] -id = "shopify-custom-access-token" -description = "Detected a Shopify custom access token, potentially compromising custom app integrations and e-commerce data security." -regex = '''shpca_[a-fA-F0-9]{32}''' -keywords = [ - "shpca_", -] - -[[rules]] -id = "shopify-private-app-access-token" -description = "Identified a Shopify private app access token, risking unauthorized access to private app data and store operations." -regex = '''shppa_[a-fA-F0-9]{32}''' -keywords = [ - "shppa_", -] - -[[rules]] -id = "shopify-shared-secret" -description = "Found a Shopify shared secret, posing a risk to application authentication and e-commerce platform security." -regex = '''shpss_[a-fA-F0-9]{32}''' -keywords = [ - "shpss_", -] - -[[rules]] -id = "sidekiq-secret" -description = "Discovered a Sidekiq Secret, which could lead to compromised background job processing and application data breaches." -regex = '''(?i)(?:BUNDLE_ENTERPRISE__CONTRIBSYS__COM|BUNDLE_GEMS__CONTRIBSYS__COM)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-f0-9]{8}:[a-f0-9]{8})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "bundle_enterprise__contribsys__com","bundle_gems__contribsys__com", -] - -[[rules]] -id = "sidekiq-sensitive-url" -description = "Uncovered a Sidekiq Sensitive URL, potentially exposing internal job queues and sensitive operation details." -regex = '''(?i)\b(http(?:s??):\/\/)([a-f0-9]{8}:[a-f0-9]{8})@(?:gems.contribsys.com|enterprise.contribsys.com)(?:[\/|\#|\?|:]|$)''' -secretGroup = 2 -keywords = [ - "gems.contribsys.com","enterprise.contribsys.com", -] - -[[rules]] -id = "slack-app-token" -description = "Detected a Slack App-level token, risking unauthorized access to Slack applications and workspace data." -regex = '''(?i)(xapp-\d-[A-Z0-9]+-\d+-[a-z0-9]+)''' -keywords = [ - "xapp", -] - -[[rules]] -id = "slack-bot-token" -description = "Identified a Slack Bot token, which may compromise bot integrations and communication channel security." -regex = '''(xoxb-[0-9]{10,13}\-[0-9]{10,13}[a-zA-Z0-9-]*)''' -keywords = [ - "xoxb", -] - -[[rules]] -id = "slack-config-access-token" -description = "Found a Slack Configuration access token, posing a risk to workspace configuration and sensitive data access." -regex = '''(?i)(xoxe.xox[bp]-\d-[A-Z0-9]{163,166})''' -keywords = [ - "xoxe.xoxb-","xoxe.xoxp-", -] - -[[rules]] -id = "slack-config-refresh-token" -description = "Discovered a Slack Configuration refresh token, potentially allowing prolonged unauthorized access to configuration settings." -regex = '''(?i)(xoxe-\d-[A-Z0-9]{146})''' -keywords = [ - "xoxe-", -] - -[[rules]] -id = "slack-legacy-bot-token" -description = "Uncovered a Slack Legacy bot token, which could lead to compromised legacy bot operations and data exposure." -regex = '''(xoxb-[0-9]{8,14}\-[a-zA-Z0-9]{18,26})''' -keywords = [ - "xoxb", -] - -[[rules]] -id = "slack-legacy-token" -description = "Detected a Slack Legacy token, risking unauthorized access to older Slack integrations and user data." -regex = '''(xox[os]-\d+-\d+-\d+-[a-fA-F\d]+)''' -keywords = [ - "xoxo","xoxs", -] - -[[rules]] -id = "slack-legacy-workspace-token" -description = "Identified a Slack Legacy Workspace token, potentially compromising access to workspace data and legacy features." -regex = '''(xox[ar]-(?:\d-)?[0-9a-zA-Z]{8,48})''' -keywords = [ - "xoxa","xoxr", -] - -[[rules]] -id = "slack-user-token" -description = "Found a Slack User token, posing a risk of unauthorized user impersonation and data access within Slack workspaces." -regex = '''(xox[pe](?:-[0-9]{10,13}){3}-[a-zA-Z0-9-]{28,34})''' -keywords = [ - "xoxp-","xoxe-", -] - -[[rules]] -id = "slack-webhook-url" -description = "Discovered a Slack Webhook, which could lead to unauthorized message posting and data leakage in Slack channels." -regex = '''(https?:\/\/)?hooks.slack.com\/(services|workflows)\/[A-Za-z0-9+\/]{43,46}''' -keywords = [ - "hooks.slack.com", -] - -[[rules]] -id = "snyk-api-token" -description = "Uncovered a Snyk API token, potentially compromising software vulnerability scanning and code security." -regex = '''(?i)(?:snyk_token|snyk_key|snyk_api_token|snyk_api_key|snyk_oauth_token)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "snyk_token","snyk_key","snyk_api_token","snyk_api_key","snyk_oauth_token", -] - -[[rules]] -id = "square-access-token" -description = "Detected a Square Access Token, risking unauthorized payment processing and financial transaction exposure." -regex = '''(?i)\b(sq0atp-[0-9A-Za-z\-_]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "sq0atp-", -] - -[[rules]] -id = "squarespace-access-token" -description = "Identified a Squarespace Access Token, which may compromise website management and content control on Squarespace." -regex = '''(?i)(?:squarespace)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "squarespace", -] - -[[rules]] -id = "stripe-access-token" -description = "Found a Stripe Access Token, posing a risk to payment processing services and sensitive financial data." -regex = '''(?i)\b((sk)_(test|live)_[0-9a-z]{10,32})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "sk_test","sk_live", -] - -[[rules]] -id = "sumologic-access-id" -description = "Discovered a SumoLogic Access ID, potentially compromising log management services and data analytics integrity." -regex = '''(?i:(?:sumo)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3})(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(su[a-zA-Z0-9]{12})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -entropy = 3 -keywords = [ - "sumo", -] - -[rules.allowlist] - -regexTarget = "line" -regexes = [ - "sumOf", -] - -[[rules]] -id = "sumologic-access-token" -description = "Uncovered a SumoLogic Access Token, which could lead to unauthorized access to log data and analytics insights." -regex = '''(?i)(?:sumo)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{64})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -entropy = 3 -keywords = [ - "sumo", -] - -[[rules]] -id = "telegram-bot-api-token" -description = "Detected a Telegram Bot API Token, risking unauthorized bot operations and message interception on Telegram." -regex = '''(?i)(?:^|[^0-9])([0-9]{5,16}:A[a-zA-Z0-9_\-]{34})(?:$|[^a-zA-Z0-9_\-])''' -keywords = [ - "telegram","api","bot","token","url", -] - -[[rules]] -id = "travisci-access-token" -description = "Identified a Travis CI Access Token, potentially compromising continuous integration services and codebase security." -regex = '''(?i)(?:travis)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{22})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "travis", -] - -[[rules]] -id = "twilio-api-key" -description = "Found a Twilio API Key, posing a risk to communication services and sensitive customer interaction data." -regex = '''SK[0-9a-fA-F]{32}''' -keywords = [ - "twilio", -] - -[[rules]] -id = "twitch-api-token" -description = "Discovered a Twitch API token, which could compromise streaming services and account integrations." -regex = '''(?i)(?:twitch)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{30})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "twitch", -] - -[[rules]] -id = "twitter-access-secret" -description = "Uncovered a Twitter Access Secret, potentially risking unauthorized Twitter integrations and data breaches." -regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{45})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "twitter", -] - -[[rules]] -id = "twitter-access-token" -description = "Detected a Twitter Access Token, posing a risk of unauthorized account operations and social media data exposure." -regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([0-9]{15,25}-[a-zA-Z0-9]{20,40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "twitter", -] - -[[rules]] -id = "twitter-api-key" -description = "Identified a Twitter API Key, which may compromise Twitter application integrations and user data security." -regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{25})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "twitter", -] - -[[rules]] -id = "twitter-api-secret" -description = "Found a Twitter API Secret, risking the security of Twitter app integrations and sensitive data access." -regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{50})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "twitter", -] - -[[rules]] -id = "twitter-bearer-token" -description = "Discovered a Twitter Bearer Token, potentially compromising API access and data retrieval from Twitter." -regex = '''(?i)(?:twitter)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(A{22}[a-zA-Z0-9%]{80,100})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "twitter", -] - -[[rules]] -id = "typeform-api-token" -description = "Uncovered a Typeform API token, which could lead to unauthorized survey management and data collection." -regex = '''(?i)(?:typeform)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(tfp_[a-z0-9\-_\.=]{59})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "tfp_", -] - -[[rules]] -id = "vault-batch-token" -description = "Detected a Vault Batch Token, risking unauthorized access to secret management services and sensitive data." -regex = '''(?i)\b(hvb\.[a-z0-9_-]{138,212})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "hvb", -] - -[[rules]] -id = "vault-service-token" -description = "Identified a Vault Service Token, potentially compromising infrastructure security and access to sensitive credentials." -regex = '''(?i)\b(hvs\.[a-z0-9_-]{90,100})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "hvs", -] - -[[rules]] -id = "yandex-access-token" -description = "Found a Yandex Access Token, posing a risk to Yandex service integrations and user data privacy." -regex = '''(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(t1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "yandex", -] - -[[rules]] -id = "yandex-api-key" -description = "Discovered a Yandex API Key, which could lead to unauthorized access to Yandex services and data manipulation." -regex = '''(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(AQVN[A-Za-z0-9_\-]{35,38})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "yandex", -] - -[[rules]] -id = "yandex-aws-access-token" -description = "Uncovered a Yandex AWS Access Token, potentially compromising cloud resource access and data security on Yandex Cloud." -regex = '''(?i)(?:yandex)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}(YC[a-zA-Z0-9_\-]{38})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "yandex", -] - -[[rules]] -id = "zendesk-secret-key" -description = "Detected a Zendesk Secret Key, risking unauthorized access to customer support services and sensitive ticketing data." -regex = '''(?i)(?:zendesk)(?:[0-9a-z\-_\t .]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:{1,3}=|\|\|:|<=|=>|:|\?=)(?:'|\"|\s|=|\x60){0,5}([a-z0-9]{40})(?:['|\"|\n|\r|\s|\x60|;]|$)''' -keywords = [ - "zendesk", -] diff --git a/sechub-pds-solutions/gitleaks/docker/mocks/mock.sarif.json b/sechub-pds-solutions/gitleaks/docker/mocks/mock.sarif.json index ff14dd1ba2..8ecc795b6c 100644 --- a/sechub-pds-solutions/gitleaks/docker/mocks/mock.sarif.json +++ b/sechub-pds-solutions/gitleaks/docker/mocks/mock.sarif.json @@ -1100,6 +1100,37 @@ "date": "", "commitMessage": "" } + }, + { + "message": { + "text": "github-pat has detected secret for file UnSAFE_Bank/iOS/Source Code/Pods/README.adoc." + }, + "ruleId": "github-pat", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "UnSAFE_Bank/iOS/Source Code/Pods/README.adoc" + }, + "region": { + "startLine": 1, + "startColumn": 1, + "endLine": 1, + "endColumn": 40, + "snippet": { + "text": "invalid-token" + } + } + } + } + ], + "partialFingerprints": { + "commitSha": "", + "email": "", + "author": "", + "date": "", + "commitMessage": "" + } } ] } diff --git a/sechub-pds-solutions/gitleaks/docker/scripts/gitleaks.sh b/sechub-pds-solutions/gitleaks/docker/scripts/gitleaks.sh index b732badf39..d3d30dc4df 100755 --- a/sechub-pds-solutions/gitleaks/docker/scripts/gitleaks.sh +++ b/sechub-pds-solutions/gitleaks/docker/scripts/gitleaks.sh @@ -1,6 +1,18 @@ -#!/bin/sh +#!/usr/bin/bash # SPDX-License-Identifier: MIT +declare -r secretvalidation_wrapper="$TOOL_FOLDER/sechub-wrapper-secret-validator.jar" + +if [[ "$PDS_INTEGRATIONTEST_ENABLED" = "true" ]]; then + echo "Integrationtest will be performed. Gitleaks will not be executed." + + # Execute the wrapper using the 'integrationtest' profile + java -jar "-Dspring.profiles.active=integrationtest" "$secretvalidation_wrapper" + + exit $? +fi + + . "$SCRIPT_FOLDER/common.sh" echo "Look for potential .git folder to perform history scan." diff --git a/sechub-pds-solutions/gitleaks/docker/sechub-wrapper-secret-validator-config.json b/sechub-pds-solutions/gitleaks/docker/sechub-wrapper-secret-validator-config.json new file mode 100644 index 0000000000..a12961f402 --- /dev/null +++ b/sechub-pds-solutions/gitleaks/docker/sechub-wrapper-secret-validator-config.json @@ -0,0 +1,21 @@ +{ + "validatorConfigList" : [ { + "ruleId" : "github-pat", + "categorization" : { + "defaultSeverity" : "high", + "validationFailedSeverity" : "medium", + "validationSuccessSeverity" : "critical" + }, + "requests" : [ { + "proxyRequired" : true, + "url" : "https://api.github.com", + "headers" : [ { + "name" : "Authorization", + "valuePrefix" : "token" + } ], + "expectedResponse" : { + "httpStatus" : 200 + } + } ] + } ] +} \ No newline at end of file diff --git a/sechub-pds-solutions/gitleaks/env b/sechub-pds-solutions/gitleaks/env index 19fe0b93e5..e2e79cb2a6 100644 --- a/sechub-pds-solutions/gitleaks/env +++ b/sechub-pds-solutions/gitleaks/env @@ -5,4 +5,4 @@ BASE_IMAGE="ghcr.io/mercedes-benz/sechub/pds-base" # The gitleaks version to use # See: https://github.com/gitleaks/gitleaks/releases -GITLEAKS_VERSION="8.18.2" +GITLEAKS_VERSION="8.18.4" diff --git a/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/Chart.yaml b/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/Chart.yaml index a75e98f502..6e80a34550 100644 --- a/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/Chart.yaml +++ b/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-gitleaks description: SecHub PDS + Gitleaks as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Rouven Haertel - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.0 +version: 1.1.0 diff --git a/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/templates/deployment.yaml b/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/templates/deployment.yaml index b44917efad..8e2a77ef87 100644 --- a/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/templates/deployment.yaml +++ b/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/values.yaml b/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/values.yaml index fee369accd..f603a9c855 100644 --- a/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/values.yaml +++ b/sechub-pds-solutions/gitleaks/helm/pds-gitleaks/values.yaml @@ -5,77 +5,88 @@ replicaCount: 1 image: - registry: "ghcr.io/mercedes-benz/sechub/pds-gitleaks" - tag: "latest" + registry: "ghcr.io/mercedes-benz/sechub/pds-gitleaks" + tag: "latest" resources: - requests: - # Initial container memory size - memory: 256Mi - limits: - # Maximum container memory size - memory: 1Gi + requests: + # Initial container memory size + memory: 256Mi + limits: + # Maximum container memory size + memory: 1Gi pds: - startMode: localserver - # Maximum upload size for source code: 50 MiB by default (50 * 1024 * 1024 = 52428800) - maxFileUploadBytes: "52428800" - config: - execute: - # Maximal accepted queue size (new job requests will be denied and thus cached on SecHub server) - queueMax: 10 - # Maximum number of jobs that are processed in parallel by PDS - workerThreadCount: 5 - heartbeatLogging: "true" - logging: - type: - enabled: false - appenderName: "LOGSTASH_JSON" - debug: - keepReportsInWorkspace: false - javaDebug: - enabled: false - keepContainerAliveAfterPDSCrashed: false + startMode: localserver + # Maximum upload size for source code: 50 MiB by default (50 * 1024 * 1024 = 52428800) + maxFileUploadBytes: "52428800" + config: + execute: + # Maximal accepted queue size (new job requests will be denied and thus cached on SecHub server) + queueMax: 10 + # Maximum number of jobs that are processed in parallel by PDS + workerThreadCount: 5 + archive: + # Limiting parameters regarding the extraction of .zip archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m + heartbeatLogging: "true" + logging: + type: + enabled: false + appenderName: "LOGSTASH_JSON" + debug: + keepReportsInWorkspace: false + javaDebug: + enabled: false + keepContainerAliveAfterPDSCrashed: false users: - technical: - id: "techuser" - apiToken: "" - admin: - id: "admin" - apiToken: "" + technical: + id: "techuser" + apiToken: "" + admin: + id: "admin" + apiToken: "" storage: - local: - enabled: true - s3: - enabled: false - endpoint: "https://:" - bucketname: "" - accesskey: "" - secretkey: "" - sharedVolume: - enabled: false - upload: - dir: "/mount/nfs/shares/" + local: + enabled: true + s3: + enabled: false + endpoint: "https://:" + bucketname: "" + accesskey: "" + secretkey: "" + sharedVolume: + enabled: false + upload: + dir: "/mount/nfs/shares/" database: - postgres: - enabled: false - connection: "jdbc:postgresql://database:5432/pds" - username: "pds_gitleaks" - password: "" + postgres: + enabled: false + connection: "jdbc:postgresql://database:5432/pds" + username: "pds_gitleaks" + password: "" networkPolicy: - enabled: false - ingress: - - from: - - podSelector: - matchLabels: - name: sechub-server - - podSelector: - matchLabels: - name: sechub-adminserver + enabled: false + ingress: + - from: + - podSelector: + matchLabels: + name: sechub-server + - podSelector: + matchLabels: + name: sechub-adminserver # deploymentComment (optional): # When setting to a different value than before, it forces k8s to spin up a new container. @@ -83,7 +94,7 @@ networkPolicy: deploymentComment: "my deployment comment" # optional: Add annotations to template.metadata.annotations -# Can contain multiple lines. Example for Prometheus actuator: +# Can contain multiple lines. Example for Prometheus actuator: # templateMetadataAnnotations: |- # prometheus.io/scrape: "true" # prometheus.io/probe: "true" diff --git a/sechub-pds-solutions/gosec/10-create-image.sh b/sechub-pds-solutions/gosec/10-create-image.sh index 9989b7003e..63e0c55e11 100755 --- a/sechub-pds-solutions/gosec/10-create-image.sh +++ b/sechub-pds-solutions/gosec/10-create-image.sh @@ -44,10 +44,12 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -if [[ ! -z "$GOSEC_VERSION" ]] ; then - echo ">> GoSec version: $GOSEC_VERSION" - BUILD_ARGS="$BUILD_ARGS --build-arg GOSEC_VERSION=$GOSEC_VERSION" +if [[ -z "$GOSEC_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> GoSec version: $GOSEC_VERSION" +BUILD_ARGS="$BUILD_ARGS --build-arg GOSEC_VERSION=$GOSEC_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/gosec/docker-compose_pds_gosec.yaml b/sechub-pds-solutions/gosec/docker-compose_pds_gosec.yaml index 421fc15704..d2970c3736 100644 --- a/sechub-pds-solutions/gosec/docker-compose_pds_gosec.yaml +++ b/sechub-pds-solutions/gosec/docker-compose_pds_gosec.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - GOSEC_VERSION=${GOSEC_VERSION} context: docker/ dockerfile: GoSec-Debian.dockerfile container_name: pds-gosec diff --git a/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster.yaml b/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster.yaml index c6859ef195..11f202436d 100644 --- a/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster.yaml +++ b/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - GOSEC_VERSION=${GOSEC_VERSION} context: docker/ dockerfile: GoSec-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster_object_storage.yaml b/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster_object_storage.yaml index 03ab5f2bde..c621903c44 100644 --- a/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster_object_storage.yaml +++ b/sechub-pds-solutions/gosec/docker-compose_pds_gosec_cluster_object_storage.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - GOSEC_VERSION=${GOSEC_VERSION} context: docker/ dockerfile: GoSec-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/gosec/docker-compose_pds_gosec_external-network.yaml b/sechub-pds-solutions/gosec/docker-compose_pds_gosec_external-network.yaml index aade8981f5..033757f0ef 100644 --- a/sechub-pds-solutions/gosec/docker-compose_pds_gosec_external-network.yaml +++ b/sechub-pds-solutions/gosec/docker-compose_pds_gosec_external-network.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - GOSEC_VERSION=${GOSEC_VERSION} context: docker/ dockerfile: GoSec-Debian.dockerfile container_name: pds-gosec diff --git a/sechub-pds-solutions/gosec/docker/GoSec-Debian.dockerfile b/sechub-pds-solutions/gosec/docker/GoSec-Debian.dockerfile index ce6641a5b3..7e46ba6c76 100644 --- a/sechub-pds-solutions/gosec/docker/GoSec-Debian.dockerfile +++ b/sechub-pds-solutions/gosec/docker/GoSec-Debian.dockerfile @@ -4,6 +4,8 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} +ARG GOSEC_VERSION + # The remaining arguments need to be placed after the `FROM` # See: https://ryandaniels.ca/blog/docker-dockerfile-arg-from-arg-trouble/ @@ -14,7 +16,6 @@ LABEL maintainer="SecHub FOSS Team" # Build args ARG GO="go1.21.6.linux-amd64.tar.gz" -ARG GOSEC_VERSION="2.16.0" # Environment variables in container ENV GOSEC_VERSION="${GOSEC_VERSION}" diff --git a/sechub-pds-solutions/gosec/helm/pds-gosec/Chart.yaml b/sechub-pds-solutions/gosec/helm/pds-gosec/Chart.yaml index 5e4245a676..bc75f5f30d 100644 --- a/sechub-pds-solutions/gosec/helm/pds-gosec/Chart.yaml +++ b/sechub-pds-solutions/gosec/helm/pds-gosec/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-gosec description: SecHub PDS + GoSec as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Jeremias Eppler - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/gosec/helm/pds-gosec/templates/deployment.yaml b/sechub-pds-solutions/gosec/helm/pds-gosec/templates/deployment.yaml index 58bccfd56c..6578902410 100644 --- a/sechub-pds-solutions/gosec/helm/pds-gosec/templates/deployment.yaml +++ b/sechub-pds-solutions/gosec/helm/pds-gosec/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/gosec/helm/pds-gosec/values.yaml b/sechub-pds-solutions/gosec/helm/pds-gosec/values.yaml index 6662a20f7a..04a39842a9 100644 --- a/sechub-pds-solutions/gosec/helm/pds-gosec/values.yaml +++ b/sechub-pds-solutions/gosec/helm/pds-gosec/values.yaml @@ -26,6 +26,17 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 10 + archive: + # Limiting parameters regarding the extraction of .zip archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: # Size of /workspace where the uploads are unpacked diff --git a/sechub-pds-solutions/iac/10-create-image.sh b/sechub-pds-solutions/iac/10-create-image.sh index 1640c19938..02d3a60bfe 100755 --- a/sechub-pds-solutions/iac/10-create-image.sh +++ b/sechub-pds-solutions/iac/10-create-image.sh @@ -36,12 +36,6 @@ if [[ -z "$BASE_IMAGE" ]]; then FAILED=true fi -# If "KICS_VERSION" is not set file "env" should be sourced -if [[ -z "$KICS_VERSION" ]] && [[ -f env ]]; then - echo "KICS_VERSION is not set, sourcing 'env' file" - source ./env -fi - if $FAILED ; then usage exit 1 @@ -50,10 +44,12 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -if [ -n "$KICS_VERSION" ] ; then - echo ">> KICS version: $KICS_VERSION" - BUILD_ARGS="$BUILD_ARGS --build-arg KICS_VERSION=$KICS_VERSION" +if [ -z "$KICS_VERSION" ] ; then + # source defaults + source ./env fi +echo ">> KICS version: $KICS_VERSION" +BUILD_ARGS="$BUILD_ARGS --build-arg KICS_VERSION=$KICS_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/iac/helm/pds-iac/Chart.yaml b/sechub-pds-solutions/iac/helm/pds-iac/Chart.yaml index 8f4a3b9493..fd62e8c8ec 100644 --- a/sechub-pds-solutions/iac/helm/pds-iac/Chart.yaml +++ b/sechub-pds-solutions/iac/helm/pds-iac/Chart.yaml @@ -3,15 +3,10 @@ apiVersion: v2 name: pds-iac description: SecHub PDS + IaC tools as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Jeremias Eppler - - name: Rouven Haertel - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/iac/helm/pds-iac/templates/deployment.yaml b/sechub-pds-solutions/iac/helm/pds-iac/templates/deployment.yaml index b44917efad..8e2a77ef87 100644 --- a/sechub-pds-solutions/iac/helm/pds-iac/templates/deployment.yaml +++ b/sechub-pds-solutions/iac/helm/pds-iac/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/iac/helm/pds-iac/values.yaml b/sechub-pds-solutions/iac/helm/pds-iac/values.yaml index e9fe2801de..493e4d1f91 100644 --- a/sechub-pds-solutions/iac/helm/pds-iac/values.yaml +++ b/sechub-pds-solutions/iac/helm/pds-iac/values.yaml @@ -5,77 +5,88 @@ replicaCount: 1 image: - registry: "ghcr.io/mercedes-benz/sechub/pds-iac" - tag: "latest" + registry: "ghcr.io/mercedes-benz/sechub/pds-iac" + tag: "latest" resources: - requests: - # Initial container memory size - memory: 256Mi - limits: - # Maximum container memory size - memory: 1Gi + requests: + # Initial container memory size + memory: 256Mi + limits: + # Maximum container memory size + memory: 1Gi pds: - startMode: localserver - # Maximum upload size for source code: 50 MiB by default (50 * 1024 * 1024 = 52428800) - maxFileUploadBytes: "52428800" - config: - execute: - # Maximal accepted queue size (new job requests will be denied and thus cached on SecHub server) - queueMax: 10 - # Maximum number of jobs that are processed in parallel by PDS - workerThreadCount: 10 - heartbeatLogging: "true" - logging: - type: - enabled: false - appenderName: "LOGSTASH_JSON" - debug: - keepReportsInWorkspace: false - javaDebug: - enabled: false - keepContainerAliveAfterPDSCrashed: false + startMode: localserver + # Maximum upload size for source code: 50 MiB by default (50 * 1024 * 1024 = 52428800) + maxFileUploadBytes: "52428800" + config: + execute: + # Maximal accepted queue size (new job requests will be denied and thus cached on SecHub server) + queueMax: 10 + # Maximum number of jobs that are processed in parallel by PDS + workerThreadCount: 10 + archive: + # Limiting parameters regarding the extraction of archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m + heartbeatLogging: "true" + logging: + type: + enabled: false + appenderName: "LOGSTASH_JSON" + debug: + keepReportsInWorkspace: false + javaDebug: + enabled: false + keepContainerAliveAfterPDSCrashed: false users: - technical: - id: "techuser" - apiToken: "" - admin: - id: "admin" - apiToken: "" + technical: + id: "techuser" + apiToken: "" + admin: + id: "admin" + apiToken: "" storage: - local: - enabled: true - s3: - enabled: false - endpoint: "https://:" - bucketname: "" - accesskey: "" - secretkey: "" - sharedVolume: - enabled: false - upload: - dir: "/mount/nfs/shares/" + local: + enabled: true + s3: + enabled: false + endpoint: "https://:" + bucketname: "" + accesskey: "" + secretkey: "" + sharedVolume: + enabled: false + upload: + dir: "/mount/nfs/shares/" database: - postgres: - enabled: false - connection: "jdbc:postgresql://database:5432/pds" - username: "pds_iac" - password: "" + postgres: + enabled: false + connection: "jdbc:postgresql://database:5432/pds" + username: "pds_iac" + password: "" networkPolicy: - enabled: false - ingress: - - from: - - podSelector: - matchLabels: - name: sechub-server - - podSelector: - matchLabels: - name: sechub-adminserver + enabled: false + ingress: + - from: + - podSelector: + matchLabels: + name: sechub-server + - podSelector: + matchLabels: + name: sechub-adminserver # deploymentComment (optional): # When setting to a different value than before, it forces k8s to spin up a new container. @@ -83,7 +94,7 @@ networkPolicy: deploymentComment: "my deployment comment" # optional: Add annotations to template.metadata.annotations -# Can contain multiple lines. Example for Prometheus actuator: +# Can contain multiple lines. Example for Prometheus actuator: # templateMetadataAnnotations: |- # prometheus.io/scrape: "true" # prometheus.io/probe: "true" diff --git a/sechub-pds-solutions/loc/10-create-image.sh b/sechub-pds-solutions/loc/10-create-image.sh index bc5a27c124..abd4fb2f64 100755 --- a/sechub-pds-solutions/loc/10-create-image.sh +++ b/sechub-pds-solutions/loc/10-create-image.sh @@ -43,15 +43,19 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -if [[ ! -z "$CLOC_VERSION" ]] ; then - echo ">> Cloc version: $CLOC_VERSION" - BUILD_ARGS+=" --build-arg CLOC_VERSION=$CLOC_VERSION" +if [[ -z "$CLOC_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> Cloc version: $CLOC_VERSION" +BUILD_ARGS+=" --build-arg CLOC_VERSION=$CLOC_VERSION" -if [[ ! -z "$SCC_VERSION" ]] ; then - echo ">> Scc version: $SCC_VERSION" - BUILD_ARGS+=" --build-arg SCC_VERSION=$SCC_VERSION" +if [[ -z "$SCC_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> Scc version: $SCC_VERSION" +BUILD_ARGS+=" --build-arg SCC_VERSION=$SCC_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/loc/docker-compose_pds_loc.yaml b/sechub-pds-solutions/loc/docker-compose_pds_loc.yaml index 8cd957bd58..fa9ca1339f 100644 --- a/sechub-pds-solutions/loc/docker-compose_pds_loc.yaml +++ b/sechub-pds-solutions/loc/docker-compose_pds_loc.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - CLOC_VERSION=${CLOC_VERSION} + - SCC_VERSION=${SCC_VERSION} context: docker/ dockerfile: loc-Debian.dockerfile container_name: pds-loc diff --git a/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster.yaml b/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster.yaml index fb0a13c7a6..4b321b95ae 100644 --- a/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster.yaml +++ b/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - CLOC_VERSION=${CLOC_VERSION} + - SCC_VERSION=${SCC_VERSION} context: docker/ dockerfile: loc-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster_object_storage.yaml b/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster_object_storage.yaml index a6d558b60e..b970caa2c8 100644 --- a/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster_object_storage.yaml +++ b/sechub-pds-solutions/loc/docker-compose_pds_loc_cluster_object_storage.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - CLOC_VERSION=${CLOC_VERSION} + - SCC_VERSION=${SCC_VERSION} context: docker/ dockerfile: loc-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/loc/docker-compose_pds_loc_external-network.yaml b/sechub-pds-solutions/loc/docker-compose_pds_loc_external-network.yaml index 7032ba6d2c..f6261ce85a 100644 --- a/sechub-pds-solutions/loc/docker-compose_pds_loc_external-network.yaml +++ b/sechub-pds-solutions/loc/docker-compose_pds_loc_external-network.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - CLOC_VERSION=${CLOC_VERSION} + - SCC_VERSION=${SCC_VERSION} context: docker/ dockerfile: loc-Debian.dockerfile container_name: pds-loc diff --git a/sechub-pds-solutions/loc/docker/loc-Debian.dockerfile b/sechub-pds-solutions/loc/docker/loc-Debian.dockerfile index d97e7185e3..4f278a2393 100644 --- a/sechub-pds-solutions/loc/docker/loc-Debian.dockerfile +++ b/sechub-pds-solutions/loc/docker/loc-Debian.dockerfile @@ -4,6 +4,9 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} +ARG CLOC_VERSION +ARG SCC_VERSION + # The remaining arguments need to be placed after the `FROM` # See: https://ryandaniels.ca/blog/docker-dockerfile-arg-from-arg-trouble/ @@ -12,10 +15,6 @@ LABEL org.opencontainers.image.title="SecHub loc+PDS Image" LABEL org.opencontainers.image.description="A container which combines lines of code (LoC) counting analytics tools with the SecHub Product Delegation Server (PDS)" LABEL maintainer="SecHub FOSS Team" -# Build args -ARG CLOC_VERSION="1.94" -ARG SCC_VERSION="3.1.0" - ENV CLOC_TAR="cloc-$CLOC_VERSION.tar.gz" ENV SCC_VERSION="${SCC_VERSION}" diff --git a/sechub-pds-solutions/loc/helm/pds-loc/Chart.yaml b/sechub-pds-solutions/loc/helm/pds-loc/Chart.yaml index 6276e7dc29..946e1a4566 100644 --- a/sechub-pds-solutions/loc/helm/pds-loc/Chart.yaml +++ b/sechub-pds-solutions/loc/helm/pds-loc/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-loc description: SecHub PDS + loc (lines of code) analytics tools as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Rouven Härtel - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.0 +version: 1.1.0 diff --git a/sechub-pds-solutions/loc/helm/pds-loc/templates/deployment.yaml b/sechub-pds-solutions/loc/helm/pds-loc/templates/deployment.yaml index fcdcb4e011..3822605870 100644 --- a/sechub-pds-solutions/loc/helm/pds-loc/templates/deployment.yaml +++ b/sechub-pds-solutions/loc/helm/pds-loc/templates/deployment.yaml @@ -51,6 +51,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" {{- if .Values.deploymentComment }} # Setting DEPLOYMENT_COMMENT to a different value every time forces k8s to spin up a new container. # This way, you can force deployments e.g. when secrets have changed. diff --git a/sechub-pds-solutions/loc/helm/pds-loc/values.yaml b/sechub-pds-solutions/loc/helm/pds-loc/values.yaml index 0d5461454f..57488ae63e 100644 --- a/sechub-pds-solutions/loc/helm/pds-loc/values.yaml +++ b/sechub-pds-solutions/loc/helm/pds-loc/values.yaml @@ -32,6 +32,17 @@ pds: queueMax: 10 # Number of parallel threads for processing workerThreadCount: 5 + archive: + # Limiting parameters regarding the extraction of archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: # Size of /workspace where the uploads are unpacked diff --git a/sechub-pds-solutions/multi/10-create-image.sh b/sechub-pds-solutions/multi/10-create-image.sh index 1e9ca0f496..b7a8222c78 100755 --- a/sechub-pds-solutions/multi/10-create-image.sh +++ b/sechub-pds-solutions/multi/10-create-image.sh @@ -40,6 +40,10 @@ if $FAILED ; then exit 1 fi +# Use Docker BuildKit +export BUILDKIT_PROGRESS=plain +export DOCKER_BUILDKIT=1 + echo ">> Base image: $BASE_IMAGE" docker build --pull --no-cache --build-arg BASE_IMAGE=$BASE_IMAGE --tag "$REGISTRY:$VERSION" --file docker/Multi-Debian.dockerfile docker/ docker tag "$REGISTRY:$VERSION" "$REGISTRY:latest" diff --git a/sechub-pds-solutions/multi/helm/pds-multi/Chart.yaml b/sechub-pds-solutions/multi/helm/pds-multi/Chart.yaml index 89e48f3722..f97a0b4155 100644 --- a/sechub-pds-solutions/multi/helm/pds-multi/Chart.yaml +++ b/sechub-pds-solutions/multi/helm/pds-multi/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-multi description: Multiple source scanner tools + PDS as Helm chart for Kubernetes - -maintainers: - - name: Jeremias Eppler - - name: Sven Dolderer - +home: https://github.com/mercedes-benz/sechub type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/multi/helm/pds-multi/templates/deployment.yaml b/sechub-pds-solutions/multi/helm/pds-multi/templates/deployment.yaml index cf643bcddc..cc1e10b949 100644 --- a/sechub-pds-solutions/multi/helm/pds-multi/templates/deployment.yaml +++ b/sechub-pds-solutions/multi/helm/pds-multi/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/multi/helm/pds-multi/values.yaml b/sechub-pds-solutions/multi/helm/pds-multi/values.yaml index 7d155ba796..0dbb70a5d1 100644 --- a/sechub-pds-solutions/multi/helm/pds-multi/values.yaml +++ b/sechub-pds-solutions/multi/helm/pds-multi/values.yaml @@ -26,6 +26,17 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 10 + archive: + # Limiting parameters regarding the extraction of archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: # Size of /workspace where the uploads are unpacked diff --git a/sechub-pds-solutions/owaspzap/10-create-image.sh b/sechub-pds-solutions/owaspzap/10-create-image.sh index fe3ec68471..e5a2e0eaeb 100755 --- a/sechub-pds-solutions/owaspzap/10-create-image.sh +++ b/sechub-pds-solutions/owaspzap/10-create-image.sh @@ -44,26 +44,33 @@ fi echo ">> Building \"$REGISTRY:$VERSION\"" BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" -echo ">> - From base image: $BASE_IMAGE" +echo ">> From base image: $BASE_IMAGE" # Enforce OWASPZAP_SHA256SUM is defined when building custom version of find-sec-bugs -if [[ ! -z "$OWASPZAP_VERSION" ]] ; then - echo ">> - OWASP-ZAP version: $OWASPZAP_VERSION" - BUILD_ARGS+=" --build-arg OWASPZAP_VERSION=$OWASPZAP_VERSION" - - if [[ -z "$OWASPZAP_SHA256SUM" ]] ; then - echo "FATAL: Please define sha256 checksum in OWASPZAP_SHA256SUM environment variable" - exit 1 - fi +if [[ -z "$OWASPZAP_VERSION" ]] ; then + # source defaults + source ./env +fi +echo ">> OWASP-ZAP version: $OWASPZAP_VERSION" +BUILD_ARGS+=" --build-arg OWASPZAP_VERSION=$OWASPZAP_VERSION" - echo ">> - OWASP-ZAP sha256sum: $OWASPZAP_SHA256SUM" - BUILD_ARGS+=" --build-arg OWASPZAP_SHA256SUM=$OWASPZAP_SHA256SUM" +if [[ -z "$OWASPZAP_SHA256SUM" ]] ; then + echo "FATAL: Please define sha256 checksum in OWASPZAP_SHA256SUM environment variable" + exit 1 fi +echo ">> OWASP-ZAP sha256sum: $OWASPZAP_SHA256SUM" +BUILD_ARGS+=" --build-arg OWASPZAP_SHA256SUM=$OWASPZAP_SHA256SUM" -if [[ ! -z "$OWASPZAP_WRAPPER_VERSION" ]] ; then - echo ">> - SecHub OWASP-ZAP Wrapper version: $OWASPZAP_WRAPPER_VERSION" - BUILD_ARGS+=" --build-arg OWASPZAP_WRAPPER_VERSION=$OWASPZAP_WRAPPER_VERSION" +if [[ -z "$OWASPZAP_WRAPPER_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> SecHub OWASP-ZAP Wrapper version: $OWASPZAP_WRAPPER_VERSION" +BUILD_ARGS+=" --build-arg OWASPZAP_WRAPPER_VERSION=$OWASPZAP_WRAPPER_VERSION" + +# Use Docker BuildKit +export BUILDKIT_PROGRESS=plain +export DOCKER_BUILDKIT=1 docker build --pull --no-cache $BUILD_ARGS \ --tag "$REGISTRY:$VERSION" \ diff --git a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap.yaml b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap.yaml index db50e6055f..0e25f5108e 100644 --- a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap.yaml +++ b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap.yaml @@ -6,6 +6,9 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - OWASPZAP_WRAPPER_VERSION=${OWASPZAP_WRAPPER_VERSION} + - OWASPZAP_VERSION=${OWASPZAP_VERSION} + - OWASPZAP_SHA256SUM=${OWASPZAP_SHA256SUM} context: docker/ dockerfile: Owasp-Zap-Debian.dockerfile container_name: pds-owaspzap diff --git a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster.yaml b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster.yaml index aadb040f4d..881e57b1ce 100644 --- a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster.yaml +++ b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster.yaml @@ -6,6 +6,9 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - OWASPZAP_WRAPPER_VERSION=${OWASPZAP_WRAPPER_VERSION} + - OWASPZAP_VERSION=${OWASPZAP_VERSION} + - OWASPZAP_SHA256SUM=${OWASPZAP_SHA256SUM} context: docker/ dockerfile: Owasp-Zap-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster_object_storage.yaml b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster_object_storage.yaml index 9838d40085..26bc098e13 100644 --- a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster_object_storage.yaml +++ b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_cluster_object_storage.yaml @@ -6,6 +6,9 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - OWASPZAP_WRAPPER_VERSION=${OWASPZAP_WRAPPER_VERSION} + - OWASPZAP_VERSION=${OWASPZAP_VERSION} + - OWASPZAP_SHA256SUM=${OWASPZAP_SHA256SUM} context: docker/ dockerfile: Owasp-Zap-Debian.dockerfile env_file: diff --git a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_external_network.yaml b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_external_network.yaml index 6ef72c4df4..6108bcb2fd 100644 --- a/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_external_network.yaml +++ b/sechub-pds-solutions/owaspzap/docker-compose_pds_owasp_zap_external_network.yaml @@ -6,6 +6,9 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - OWASPZAP_WRAPPER_VERSION=${OWASPZAP_WRAPPER_VERSION} + - OWASPZAP_VERSION=${OWASPZAP_VERSION} + - OWASPZAP_SHA256SUM=${OWASPZAP_SHA256SUM} context: docker/ dockerfile: Owasp-Zap-Debian.dockerfile container_name: pds-owaspzap diff --git a/sechub-pds-solutions/owaspzap/docker/Owasp-Zap-Debian.dockerfile b/sechub-pds-solutions/owaspzap/docker/Owasp-Zap-Debian.dockerfile index e27e50fe37..3f360047fe 100644 --- a/sechub-pds-solutions/owaspzap/docker/Owasp-Zap-Debian.dockerfile +++ b/sechub-pds-solutions/owaspzap/docker/Owasp-Zap-Debian.dockerfile @@ -4,18 +4,18 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} +# Build args +# ZAP (Zed Attack Proxy) version. See https://github.com/zaproxy/zaproxy +ARG OWASPZAP_VERSION +ARG OWASPZAP_SHA256SUM +# Version of SecHub's ZAP wrapper +ARG OWASPZAP_WRAPPER_VERSION + LABEL org.opencontainers.image.source="https://github.com/mercedes-benz/sechub" LABEL org.opencontainers.image.title="SecHub OWASP ZAP + PDS Image" LABEL org.opencontainers.image.description="A container which combines OWASP ZAP with the SecHub Product Delegation Server (PDS)" LABEL maintainer="SecHub FOSS Team" -# Build args -# ZAP (Zed Attack Proxy) version. See https://github.com/zaproxy/zaproxy -ARG OWASPZAP_VERSION="2.14.0" -ARG OWASPZAP_SHA256SUM="219d7f25bbe25247713805ab02cc12279898c870743c1aae3c2b0b1882191960" - -ARG OWASPZAP_WRAPPER_VERSION="1.6.0" - # OWASP ZAP host and port ENV ZAP_HOST="127.0.0.1" ENV ZAP_PORT="8080" diff --git a/sechub-pds-solutions/owaspzap/docker/pds-config.json b/sechub-pds-solutions/owaspzap/docker/pds-config.json index 39bff4562c..d5d234f13e 100644 --- a/sechub-pds-solutions/owaspzap/docker/pds-config.json +++ b/sechub-pds-solutions/owaspzap/docker/pds-config.json @@ -5,7 +5,6 @@ { "id": "PDS_OWASP_ZAP", "path": "/pds/scripts/owasp-zap.sh", - "envWhitelist" : [ "TOOL_FOLDER" ], "scanType": "webScan", "minutesToWaitForProductResult": 600, "description": "Runs OWASP ZAP", @@ -70,7 +69,13 @@ "default": 1000 } ] - } + }, + "envWhitelist": [ + "TOOL_FOLDER", + "ZAP_API_KEY", + "ZAP_HOST", + "ZAP_PORT" + ] }, { "id": "PDS_OWASP_ZAP_MOCK", diff --git a/sechub-pds-solutions/owaspzap/docker/scripts/owasp-zap.sh b/sechub-pds-solutions/owaspzap/docker/scripts/owasp-zap.sh index 185990999b..5d05761c01 100755 --- a/sechub-pds-solutions/owaspzap/docker/scripts/owasp-zap.sh +++ b/sechub-pds-solutions/owaspzap/docker/scripts/owasp-zap.sh @@ -6,6 +6,25 @@ shutdownZAP() { pkill -9 --full "/pds/tools/ZAP_" } +function check_env_var_is_set { + local param="$1" + if [ -z "${!param}" ] ; then + echo "Mandatory environment variable $param is not set!" + failed=true + fi +} + +# Check if mandatory environment variables are set +MANDATORY_ENV_VARS="PDS_SCAN_TARGET_TYPE PDS_SCAN_TARGET_URL SECHUB_JOB_UUID ZAP_ACTIVESCAN_ENABLED ZAP_AJAXCRAWLER_ENABLED ZAP_API_KEY ZAP_HOST ZAP_JVM_ARGS ZAP_PORT" +failed=false +for i in $MANDATORY_ENV_VARS ; do + check_env_var_is_set $i +done +if $failed ; then + echo "Please make sure that mandatory environment variables are passed to this script. Exiting." + exit 1 +fi + # Start OWASP-ZAP server echo "Starting up OWASP-ZAP server" # -silent: disables telemetry calls, of the call home addon: https://www.zaproxy.org/docs/desktop/addons/call-home/ @@ -29,8 +48,7 @@ TOTAL_WAITTIME=$(($RETRIES*$SECONDS_BEFORE_RETRY)) # --no-check-certificate: OWASP-ZAP uses a self-signed certificate, because of this we skip the certificate validation wget --quiet --output-document=- --retry-connrefused --tries="$RETRIES" --waitretry="$SECONDS_BEFORE_RETRY" --header="Accept: application/json" --header="X-ZAP-API-Key: $ZAP_API_KEY" "http://$ZAP_HOST:$ZAP_PORT/JSON/core/view/version" -if [ $? -ne 0 ] -then +if [ $? -ne 0 ] ; then echo "OWASP-ZAP did not start after waiting for $TOTAL_WAITTIME seconds" shutdownZAP exit 1 @@ -42,7 +60,7 @@ echo "" options="" -if [[ "$PDS_WRAPPER_REMOTE_DEBUGGING_ENABLED" = "true" ]]; then +if [[ "$PDS_WRAPPER_REMOTE_DEBUGGING_ENABLED" = "true" ]] ; then options="$options -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000" fi @@ -56,54 +74,47 @@ echo "" zap_options="" -if [ "$ZAP_ACTIVESCAN_ENABLED" = "true" ] -then +if [ "$ZAP_ACTIVESCAN_ENABLED" = "true" ] ; then echo "Active scan: enabled" zap_options="$zap_options --activeScan" else echo "Active scan: disabled" fi -if [ "$ZAP_AJAXCRAWLER_ENABLED" = "true" ] -then +if [ "$ZAP_AJAXCRAWLER_ENABLED" = "true" ] ; then echo "Ajax spider: enabled" zap_options="$zap_options --ajaxSpider" else echo "Ajax spider: disabled" fi -if [ "$ZAP_USE_PROXY" != "true" ] && [ "$ZAP_PROXY_FOR_PDS_TARGET_TYPE" = "$PDS_SCAN_TARGET_TYPE" ] -then +if [ "$ZAP_USE_PROXY" != "true" ] && [ "$ZAP_PROXY_FOR_PDS_TARGET_TYPE" = "$PDS_SCAN_TARGET_TYPE" ] ; then ZAP_USE_PROXY="true" echo "Using proxy for target type: $PDS_SCAN_TARGET_TYPE" fi -if [ "$ZAP_USE_PROXY" = "true" ] -then +if [ "$ZAP_USE_PROXY" = "true" ] ; then echo "Use proxy: enabled" zap_options="$zap_options --proxyHost $ZAP_PROXY_HOST --proxyPort $ZAP_PROXY_PORT" else echo "Use proxy: disabled" fi -if [ "$WRAPPER_CONNECTIONCHECK_ENABLED" = "true" ] -then +if [ "$WRAPPER_CONNECTIONCHECK_ENABLED" = "true" ] ; then echo "Wrapper connection check: enabled" zap_options="$zap_options --connectionCheck" else echo "Wrapper connection check: disabled" fi -if [ ! -z "$WRAPPER_MAXIMUM_CONNECTION_RETRIES" ] -then +if [ ! -z "$WRAPPER_MAXIMUM_CONNECTION_RETRIES" ] ; then echo "Use WRAPPER_MAXIMUM_CONNECTION_RETRIES: $WRAPPER_MAXIMUM_CONNECTION_RETRIES" zap_options="$zap_options --maxNumberOfConnectionRetries $WRAPPER_MAXIMUM_CONNECTION_RETRIES" else echo "Use default value of wrapper for WRAPPER_MAXIMUM_CONNECTION_RETRIES" fi -if [ ! -z "$WRAPPER_RETRY_WAITTIME_MILLISECONDS" ] -then +if [ ! -z "$WRAPPER_RETRY_WAITTIME_MILLISECONDS" ] ; then echo "Use WRAPPER_RETRY_WAITTIME_MILLISECONDS: $WRAPPER_RETRY_WAITTIME_MILLISECONDS" zap_options="$zap_options --retryWaittimeInMilliseconds $WRAPPER_RETRY_WAITTIME_MILLISECONDS" else @@ -114,14 +125,10 @@ echo "" echo "Start scanning" echo "" -if [ ! -z "$PDS_SCAN_CONFIGURATION" ] -then +if [ ! -z "$PDS_SCAN_CONFIGURATION" ] ; then sechub_scan_configuration="$PDS_JOB_WORKSPACE_LOCATION/sechubScanConfiguration.json" - echo "Using configuration file: $sechub_scan_configuration" - echo "$PDS_SCAN_CONFIGURATION" > "$sechub_scan_configuration" - zap_options="$zap_options --sechubConfigfile $sechub_scan_configuration" fi @@ -132,5 +139,5 @@ echo "Shutdown OWASP-ZAP after scan" shutdownZAP echo "Clean up possible old session data after scan" -rm -r ~/.ZAP/session/* +rm -rf ~/.ZAP/session/* rm ~/.ZAP/zap.log* diff --git a/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/Chart.yaml b/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/Chart.yaml index 9ba7979410..b12c69839f 100644 --- a/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/Chart.yaml +++ b/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/Chart.yaml @@ -3,15 +3,10 @@ apiVersion: v2 name: pds-owaspzap description: The OWASP ZAP + PDS as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Jan Winz - - name: Jeremias Eppler - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.2.0 +version: 1.3.0 diff --git a/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/templates/deployment.yaml b/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/templates/deployment.yaml index 04214287ee..4b6e4db9a3 100644 --- a/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/templates/deployment.yaml +++ b/sechub-pds-solutions/owaspzap/helm/pds-owaspzap/templates/deployment.yaml @@ -92,11 +92,11 @@ spec: - name: DATABASE_PASSWORD value: "{{ .Values.database.postgres.password }}" {{- end }} - # limit database connection pool to one + # limit database connection pool - name: SPRING_DATASOURCE_HIKARI_MINIMUMIDLE value: "1" - name: SPRING_DATASOURCE_HIKARI_MAXIMUMPOOLSIZE - value: "1" + value: "2" # Storage priority in order: local, s3, shared volume # Meaning if local is enabled local will be used, diff --git a/sechub-pds-solutions/pmd/10-create-image.sh b/sechub-pds-solutions/pmd/10-create-image.sh index 46c21b01c0..a70306eb63 100755 --- a/sechub-pds-solutions/pmd/10-create-image.sh +++ b/sechub-pds-solutions/pmd/10-create-image.sh @@ -43,10 +43,16 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -if [[ ! -z "$PMD_VERSION" ]] ; then - echo ">> PMD version: $PMD_VERSION" - BUILD_ARGS+=" --build-arg PMD_VERSION=$PMD_VERSION" +if [[ -z "$PMD_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> PMD version: $PMD_VERSION" +BUILD_ARGS+=" --build-arg PMD_VERSION=$PMD_VERSION" + +# Use Docker BuildKit +export BUILDKIT_PROGRESS=plain +export DOCKER_BUILDKIT=1 docker build --pull --no-cache $BUILD_ARGS \ --tag "$REGISTRY:$VERSION" \ diff --git a/sechub-pds-solutions/pmd/docker-compose_pds_pmd.yaml b/sechub-pds-solutions/pmd/docker-compose_pds_pmd.yaml index 0f900083bd..6538388aa3 100644 --- a/sechub-pds-solutions/pmd/docker-compose_pds_pmd.yaml +++ b/sechub-pds-solutions/pmd/docker-compose_pds_pmd.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - PMD_VERSION=${PMD_VERSION} context: docker/ dockerfile: PMD-Debian.dockerfile container_name: pds-pmd diff --git a/sechub-pds-solutions/pmd/docker-compose_pds_pmd_external-network.yaml b/sechub-pds-solutions/pmd/docker-compose_pds_pmd_external-network.yaml index a966e1df2a..d62235d8c5 100644 --- a/sechub-pds-solutions/pmd/docker-compose_pds_pmd_external-network.yaml +++ b/sechub-pds-solutions/pmd/docker-compose_pds_pmd_external-network.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - PMD_VERSION=${PMD_VERSION} context: docker/ dockerfile: PMD-Debian.dockerfile container_name: pds-pmd diff --git a/sechub-pds-solutions/pmd/docker/PMD-Debian.dockerfile b/sechub-pds-solutions/pmd/docker/PMD-Debian.dockerfile index a9de020c3f..1fb497a64b 100644 --- a/sechub-pds-solutions/pmd/docker/PMD-Debian.dockerfile +++ b/sechub-pds-solutions/pmd/docker/PMD-Debian.dockerfile @@ -4,21 +4,17 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} -# The remaining arguments need to be placed after the `FROM` -# See: https://ryandaniels.ca/blog/docker-dockerfile-arg-from-arg-trouble/ +ARG PMD_VERSION LABEL org.opencontainers.image.source="https://github.com/mercedes-benz/sechub" LABEL org.opencontainers.image.title="SecHub PMD+PDS Image" LABEL org.opencontainers.image.description="A container which combines PMD with the SecHub Product Delegation Server (PDS)" LABEL maintainer="SecHub FOSS Team" -# Build args -ARG PMD_VERSION="6.55.0" - # Environment variables in container ENV PMD_VERSION="${PMD_VERSION}" -user root +USER root # Copy mock folder COPY mocks "$MOCK_FOLDER" diff --git a/sechub-pds-solutions/pmd/helm/pds-pmd/Chart.yaml b/sechub-pds-solutions/pmd/helm/pds-pmd/Chart.yaml index 1be8d63700..27e38bfbc1 100644 --- a/sechub-pds-solutions/pmd/helm/pds-pmd/Chart.yaml +++ b/sechub-pds-solutions/pmd/helm/pds-pmd/Chart.yaml @@ -3,15 +3,10 @@ apiVersion: v2 name: pds-pmd description: SecHub PDS + PMD as Helm chart for Kubernetes - -maintainers: - - name: Jeremias Eppler - - name: Sven Dolderer - - name: Valentyn Grygoriev - +home: https://github.com/mercedes-benz/sechub type: application -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/pmd/helm/pds-pmd/templates/deployment.yaml b/sechub-pds-solutions/pmd/helm/pds-pmd/templates/deployment.yaml index cf643bcddc..cc1e10b949 100644 --- a/sechub-pds-solutions/pmd/helm/pds-pmd/templates/deployment.yaml +++ b/sechub-pds-solutions/pmd/helm/pds-pmd/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/pmd/helm/pds-pmd/values.yaml b/sechub-pds-solutions/pmd/helm/pds-pmd/values.yaml index 07e50e3cfc..7f157379fc 100644 --- a/sechub-pds-solutions/pmd/helm/pds-pmd/values.yaml +++ b/sechub-pds-solutions/pmd/helm/pds-pmd/values.yaml @@ -26,6 +26,17 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 10 + archive: + # Limiting parameters regarding the extraction of archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: # Size of /workspace where the uploads are unpacked diff --git a/sechub-pds-solutions/prepare/10-create-image.sh b/sechub-pds-solutions/prepare/10-create-image.sh index 7e9b045a65..f435fcccca 100755 --- a/sechub-pds-solutions/prepare/10-create-image.sh +++ b/sechub-pds-solutions/prepare/10-create-image.sh @@ -52,17 +52,19 @@ if [[ -z "$BUILD_TYPE" ]] ; then BUILD_TYPE="$DEFAULT_BUILD_TYPE" fi BUILD_ARGS+=" --build-arg BUILD_TYPE=$BUILD_TYPE" -echo ">> - Build type: $BUILD_TYPE" +echo ">> Build type: $BUILD_TYPE" if [[ ! -z "$BUILDER_BASE_IMAGE" ]] ; then BUILD_ARGS+=" --build-arg BUILDER_BASE_IMAGE=$BUILDER_BASE_IMAGE" - echo ">> - Builder base image: $BUILDER_BASE_IMAGE" + echo ">> Builder base image: $BUILDER_BASE_IMAGE" fi -if [[ ! -z "$PREPARE_WRAPPER_VERSION" ]] ; then - echo ">> Prepare version: $PREPARE_WRAPPER_VERSION" - BUILD_ARGS="$BUILD_ARGS --build-arg PREPARE_WRAPPER_VERSION=$PREPARE_WRAPPER_VERSION" +if [[ -z "$PREPARE_WRAPPER_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> Prepare wrapper version: $PREPARE_WRAPPER_VERSION" +BUILD_ARGS+=" --build-arg PREPARE_WRAPPER_VERSION=$PREPARE_WRAPPER_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/prepare/docker-compose_pds_prepare.yaml b/sechub-pds-solutions/prepare/docker-compose_pds_prepare.yaml index 208655c2d1..031f60b69f 100644 --- a/sechub-pds-solutions/prepare/docker-compose_pds_prepare.yaml +++ b/sechub-pds-solutions/prepare/docker-compose_pds_prepare.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - PREPARE_WRAPPER_VERSION=${PREPARE_WRAPPER_VERSION} context: docker/ dockerfile: Prepare-Debian.dockerfile container_name: pds-prepare diff --git a/sechub-pds-solutions/prepare/docker-compose_pds_prepare_external-network.yaml b/sechub-pds-solutions/prepare/docker-compose_pds_prepare_external-network.yaml index 2623d1821f..3c2b1097ad 100644 --- a/sechub-pds-solutions/prepare/docker-compose_pds_prepare_external-network.yaml +++ b/sechub-pds-solutions/prepare/docker-compose_pds_prepare_external-network.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - PREPARE_WRAPPER_VERSION=${PREPARE_WRAPPER_VERSION} context: docker/ dockerfile: Prepare-Debian.dockerfile container_name: pds-prepare diff --git a/sechub-pds-solutions/prepare/docker/Prepare-Debian.dockerfile b/sechub-pds-solutions/prepare/docker/Prepare-Debian.dockerfile index 75ed7d8b55..e2675b4182 100644 --- a/sechub-pds-solutions/prepare/docker/Prepare-Debian.dockerfile +++ b/sechub-pds-solutions/prepare/docker/Prepare-Debian.dockerfile @@ -6,7 +6,7 @@ ARG BASE_IMAGE # Build Args # Build type can be "copy" or "download" ARG BUILD_TYPE="copy" -ARG PREPARE_WRAPPER_VERSION="0.0.0" +ARG PREPARE_WRAPPER_VERSION # The base image of the builder ARG BUILDER_BASE_IMAGE="debian:12-slim" @@ -33,11 +33,11 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ # Download the prepare Wrapper RUN cd "$ARTIFACT_FOLDER" && \ # download wrapper jar - wget --no-verbose "https://github.com/mercedes-benz/sechub/releases/download/v$PREPARE_WRAPPER_VERSION-prepare-wrapper/sechub-pds-wrapper-prepare-$PREPARE_WRAPPER_VERSION.jar" && \ + wget --no-verbose "https://github.com/mercedes-benz/sechub/releases/download/v$PREPARE_WRAPPER_VERSION-prepare-wrapper/sechub-wrapper-prepare-$PREPARE_WRAPPER_VERSION.jar" && \ # download checksum file - wget --no-verbose "https://github.com/mercedes-benz/sechub/releases/download/v$PREPARE_WRAPPER_VERSION-prepare-wrapper/sechub-pds-wrapper-prepare-$PREPARE_WRAPPER_VERSION.jar.sha256sum" && \ + wget --no-verbose "https://github.com/mercedes-benz/sechub/releases/download/v$PREPARE_WRAPPER_VERSION-prepare-wrapper/sechub-wrapper-prepare-$PREPARE_WRAPPER_VERSION.jar.sha256sum" && \ # verify the checksum - sha256sum --check "sechub-pds-wrapper-prepare-$PREPARE_WRAPPER_VERSION.jar.sha256sum" + sha256sum --check "sechub-wrapper-prepare-$PREPARE_WRAPPER_VERSION.jar.sha256sum" #------------------- @@ -60,7 +60,7 @@ COPY copy/sechub-wrapper-prepare-$PREPARE_WRAPPER_VERSION.jar "$ARTIFACT_FOLDER" # Builder #------------------- -FROM builder-${BUILD_TYPE} as builder +FROM builder-${BUILD_TYPE} AS builder RUN echo "build stage" diff --git a/sechub-pds-solutions/prepare/docker/mocks/prepare-mock-status-ok.txt b/sechub-pds-solutions/prepare/docker/mocks/prepare-mock-status-ok.txt new file mode 100644 index 0000000000..a637007a82 --- /dev/null +++ b/sechub-pds-solutions/prepare/docker/mocks/prepare-mock-status-ok.txt @@ -0,0 +1 @@ +SECHUB_PREPARE_RESULT;status=ok \ No newline at end of file diff --git a/sechub-pds-solutions/prepare/docker/scripts/prepare_mock.sh b/sechub-pds-solutions/prepare/docker/scripts/prepare_mock.sh index ffa5865cbe..5d302a0333 100755 --- a/sechub-pds-solutions/prepare/docker/scripts/prepare_mock.sh +++ b/sechub-pds-solutions/prepare/docker/scripts/prepare_mock.sh @@ -10,8 +10,5 @@ echo "SecHub Job UUID: $SECHUB_JOB_UUID" echo "PDS Job UUID: $PDS_JOB_UUID" echo "" -echo "SECHUB_PREPARE_RESULT;status=ok" > "$PDS_JOB_RESULT_FILE" - - - - +echo "Running PDS Prepare Mock" +cp "$MOCK_FOLDER/prepare-mock-status-ok.txt" "$PDS_JOB_RESULT_FILE" \ No newline at end of file diff --git a/sechub-pds-solutions/prepare/helm/pds-prepare/Chart.yaml b/sechub-pds-solutions/prepare/helm/pds-prepare/Chart.yaml index c463df968e..a9f0f6ea02 100644 --- a/sechub-pds-solutions/prepare/helm/pds-prepare/Chart.yaml +++ b/sechub-pds-solutions/prepare/helm/pds-prepare/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-prepare description: SecHub PDS + Prepare wrapper as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Laura Bottner - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/prepare/helm/pds-prepare/templates/deployment.yaml b/sechub-pds-solutions/prepare/helm/pds-prepare/templates/deployment.yaml index 1e264c8462..1d342084d0 100644 --- a/sechub-pds-solutions/prepare/helm/pds-prepare/templates/deployment.yaml +++ b/sechub-pds-solutions/prepare/helm/pds-prepare/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/prepare/helm/pds-prepare/values.yaml b/sechub-pds-solutions/prepare/helm/pds-prepare/values.yaml index 9ae48321de..9bd3ec2905 100644 --- a/sechub-pds-solutions/prepare/helm/pds-prepare/values.yaml +++ b/sechub-pds-solutions/prepare/helm/pds-prepare/values.yaml @@ -28,6 +28,17 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 1 + archive: + # Limiting parameters regarding the extraction of archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: size: "1Gi" diff --git a/sechub-pds-solutions/scancode/10-create-image.sh b/sechub-pds-solutions/scancode/10-create-image.sh index 4e416fd950..c3c8baba3e 100755 --- a/sechub-pds-solutions/scancode/10-create-image.sh +++ b/sechub-pds-solutions/scancode/10-create-image.sh @@ -32,7 +32,7 @@ if [[ -z "$VERSION" ]] ; then FAILED=true fi -if [[ -z "$BASE_IMAGE" ]]; then +if [[ -z "$BASE_IMAGE" ]] ; then echo "Please provide a base image as 3rd parameter." FAILED=true fi @@ -45,15 +45,23 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -if [[ -n "$SCANCODE_VERSION" ]] ; then - BUILD_ARGS+=" --build-arg SCANCODE_VERSION=$SCANCODE_VERSION" - echo ">> Scancode version: $SCANCODE_VERSION" +if [[ -z "$SCANCODE_VERSION" ]] ; then + # source defaults + source ./env fi +BUILD_ARGS+=" --build-arg SCANCODE_VERSION=$SCANCODE_VERSION" +echo ">> Scancode version: $SCANCODE_VERSION" -if [[ -n "$SPDX_TOOL_VERSION" ]] ; then - BUILD_ARGS+=" --build-arg SPDX_TOOL_VERSION=$SPDX_TOOL_VERSION" - echo ">> SPDX Tool version: $SPDX_TOOL_VERSION" +if [[ -z "$SPDX_TOOL_VERSION" ]] ; then + # source defaults + source ./env fi +BUILD_ARGS+=" --build-arg SPDX_TOOL_VERSION=$SPDX_TOOL_VERSION" +echo ">> SPDX Tool version: $SPDX_TOOL_VERSION" + +# Use Docker BuildKit +export BUILDKIT_PROGRESS=plain +export DOCKER_BUILDKIT=1 docker build --pull --no-cache $BUILD_ARGS \ --tag "$REGISTRY:$VERSION" \ diff --git a/sechub-pds-solutions/scancode/docker/Scancode-Debian.dockerfile b/sechub-pds-solutions/scancode/docker/Scancode-Debian.dockerfile index 796447ed1e..993418c728 100644 --- a/sechub-pds-solutions/scancode/docker/Scancode-Debian.dockerfile +++ b/sechub-pds-solutions/scancode/docker/Scancode-Debian.dockerfile @@ -3,17 +3,16 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} +# SCANCODE_VERSION: see https://github.com/nexB/scancode-toolkit/releases Use the version number only +ARG SCANCODE_VERSION +# SPDX_TOOL_VERSION: see https://mvnrepository.com/artifact/org.spdx/tools-java +ARG SPDX_TOOL_VERSION + LABEL org.opencontainers.image.source="https://github.com/mercedes-benz/sechub" LABEL org.opencontainers.image.title="SecHub Scancode-Toolkit+PDS Image" LABEL org.opencontainers.image.description="A container which combines Scancode-Toolkit with the SecHub Product Delegation Server (PDS)" LABEL maintainer="SecHub FOSS Team" -# Build args -# SCANCODE_VERSION: see https://github.com/nexB/scancode-toolkit/releases Use the version number only -ARG SCANCODE_VERSION="32.0.4" -# SPDX_TOOL_VERSION: see https://mvnrepository.com/artifact/org.spdx/tools-java -ARG SPDX_TOOL_VERSION="1.1.7" - # Environment variables in container ENV SCANCODE_VERSION="${SCANCODE_VERSION}" ENV SPDX_TOOL_VERSION="${SPDX_TOOL_VERSION}" diff --git a/sechub-pds-solutions/scancode/docker/pds-config.json b/sechub-pds-solutions/scancode/docker/pds-config.json index f19952e41d..9e35501a28 100644 --- a/sechub-pds-solutions/scancode/docker/pds-config.json +++ b/sechub-pds-solutions/scancode/docker/pds-config.json @@ -5,42 +5,51 @@ { "id": "PDS_SCANCODE", "path": "/pds/scripts/scancode.sh", - "envWhitelist" : [ "HELPER_FOLDER", "PDS_VERSION", "TOOL_FOLDER" ], "scanType": "licenseScan", "description": "Runs Scancode.", "minutesToWaitForProductResult": "660", "parameters": { "optional": [ { - "key": "scancode.output.format", - "description": "Parameter defining the ScanCode output format. Possible values are: json, json-pp, spdx-tv, spdx-json, spdx-rdf. Json-pp stands for json pretty printed.", - "default": "spdx-json" + "key": "extractcode.enabled", + "description": "Extractcode is a helper tool, which extracts archives before scanning. Set it to `true` to enable it.", + "default": false }, { "key": "scancode.license.score", "description": "A natural number between 0 (low macht accuracy) and 100 (high match accuracy).", "default": 0 }, + { + "key": "scancode.output.format", + "description": "Parameter defining the ScanCode output format. Possible values are: json, json-pp, spdx-tv, spdx-json, spdx-rdf. Json-pp stands for json pretty printed.", + "default": "spdx-json" + }, { "key": "scancode.processes", "description": "The number of processes used by ScanCode. A \"good\" value is the number of CPUs minus 1 (more information: https://github.com/nexB/scancode-toolkit/issues/2980#issuecomment-1146583845).", "default": 1 }, { - "key": "extractcode.enabled", - "description": "Extractcode is a helper tool, which extracts archives before scanning. Set it to `true` to enable it.", + "key": "scancode.scan.copyright", + "description": "Scan for copyrights.", "default": false }, { - "key": "scancode.timeout", - "description": "Stop scanning a file if scanning takes longer than a timeout in seconds (as natural number).", - "default": "120" + "key": "scancode.scan.diagnostics", + "description": "Diagnostic information", + "default": false }, { - "key": "scancode.scan.copyright", - "description": "Scan for copyrights.", + "key": "scancode.scan.email", + "description": "Scan for emails.", "default": false }, + { + "key": "scancode.scan.info", + "description": "Include information regarding the scan.", + "default": true + }, { "key": "scancode.scan.license", "description": "Scan for licenses.", @@ -51,28 +60,24 @@ "description": "Scan for packages.", "default": false }, - { - "key": "scancode.scan.email", - "description": "Scan for emails.", - "default": false - }, { "key": "scancode.scan.url", "description": "Scan for urls.", "default": false }, { - "key": "scancode.scan.info", - "description": "Include information regarding the scan.", - "default": true - }, - { - "key": "scancode.scan.diagnostics", - "description": "Diagnostic information", - "default": false + "key": "scancode.timeout", + "description": "Stop scanning a file if scanning takes longer than a timeout in seconds (as natural number).", + "default": "120" } ] - } + }, + "envWhitelist": [ + "HELPER_FOLDER", + "PDS_VERSION", + "SPDX_TOOL_VERSION", + "TOOL_FOLDER" + ] }, { "id": "PDS_SCANCODE_MOCK", diff --git a/sechub-pds-solutions/scancode/docker/scripts/scancode.sh b/sechub-pds-solutions/scancode/docker/scripts/scancode.sh index 601270ef93..17743c01aa 100755 --- a/sechub-pds-solutions/scancode/docker/scripts/scancode.sh +++ b/sechub-pds-solutions/scancode/docker/scripts/scancode.sh @@ -1,29 +1,46 @@ #!/usr/bin/bash # SPDX-License-Identifier: MIT +function check_env_var_is_set { + local param="$1" + if [ -z "${!param}" ] ; then + echo "Mandatory environment variable $param is not set!" + failed=true + fi +} + +# Check if mandatory environment variables are set +MANDATORY_ENV_VARS="EXTRACTCODE_ENABLED HELPER_FOLDER SCANCODE_LICENSE_SCORE SCANCODE_OUTPUT_FORMAT SCANCODE_PROCESSES SCANCODE_SCAN_COPYRIGHT SCANCODE_SCAN_DIAGNOSTICS SCANCODE_SCAN_EMAIL SCANCODE_SCAN_INFO SCANCODE_SCAN_LICENSE SCANCODE_SCAN_PACKAGE SCANCODE_SCAN_URL SCANCODE_TIMEOUT SPDX_TOOL_VERSION TOOL_FOLDER" +failed=false +for i in $MANDATORY_ENV_VARS ; do + check_env_var_is_set $i +done +if $failed ; then + echo "Please make sure that mandatory environment variables are passed to this script. Exiting." + exit 1 +fi + source "${HELPER_FOLDER}/message.sh" function convert() { - local spdx_file="$1" - local spdx_json_file="$PDS_JOB_RESULT_FILE.spdx.json" - - echo "Converting to SPDX JSON" - - if [[ ! -f "$spdx_file" ]] - then - echo "Error file $spdx_file does not exist." - return 1 - fi - - # use the SPDX tool converter to convert the SPDX tag-value to SPDX JSON - java -jar "$TOOL_FOLDER/tools-java-${SPDX_TOOL_VERSION}-jar-with-dependencies.jar" Convert "$spdx_file" "$spdx_json_file" TAG JSON - - if [[ -f "$spdx_json_file" ]] - then - mv "$spdx_json_file" "$PDS_JOB_RESULT_FILE" - else - errorMessage "Product error. Unable to convert result to SPDX JSON." - fi + local spdx_file="$1" + local spdx_json_file="$PDS_JOB_RESULT_FILE.spdx.json" + + echo "Converting to SPDX JSON" + + if [[ ! -f "$spdx_file" ]] ; then + echo "Error file $spdx_file does not exist." + return 1 + fi + + # use the SPDX tool converter to convert the SPDX tag-value to SPDX JSON + java -jar "$TOOL_FOLDER/tools-java-${SPDX_TOOL_VERSION}-jar-with-dependencies.jar" Convert "$spdx_file" "$spdx_json_file" TAG JSON + + if [[ -f "$spdx_json_file" ]] ; then + mv "$spdx_json_file" "$PDS_JOB_RESULT_FILE" + else + errorMessage "Product error. Unable to convert result to SPDX JSON." + fi } # IMPORTANT: Keep the space in front and back of the list @@ -71,22 +88,20 @@ echo "PDS Job UUID: $PDS_JOB_UUID" echo "" extracted_folder="" -if [[ "$PDS_JOB_HAS_EXTRACTED_SOURCES" == "true" ]] -then - echo "Extracted sources" - extracted_folder="$PDS_JOB_EXTRACTED_SOURCES_FOLDER" -elif [[ "$PDS_JOB_HAS_EXTRACTED_BINARIES" == "true" ]] -then - echo "Extracted binaries" - extracted_folder="$PDS_JOB_EXTRACTED_BINARIES_FOLDER" +if [[ "$PDS_JOB_HAS_EXTRACTED_SOURCES" == "true" ]] ; then + echo "Extracted sources" + extracted_folder="$PDS_JOB_EXTRACTED_SOURCES_FOLDER" +elif [[ "$PDS_JOB_HAS_EXTRACTED_BINARIES" == "true" ]] ; then + echo "Extracted binaries" + extracted_folder="$PDS_JOB_EXTRACTED_BINARIES_FOLDER" else - echo "" - echo "ERROR: Unrecognized file type. Neither binary nor source." - echo "" - echo "Workspace location structure:" - echo "" - tree "$PDS_JOB_WORKSPACE_LOCATION" - exit 1 + echo "" + echo "ERROR: Unrecognized file type. Neither binary nor source." + echo "" + echo "Workspace location structure:" + echo "" + tree "$PDS_JOB_WORKSPACE_LOCATION" + exit 1 fi echo "Extracted folder structure:" @@ -104,30 +119,25 @@ echo "--------------" echo "" echo "User provided number of scancode processes: $SCANCODE_PROCESSES" -if [[ "$SCANCODE_PROCESSES" -ge 1 ]] -then - scancode_processes="$SCANCODE_PROCESSES" - options="$options --processes $scancode_processes" +if [[ "$SCANCODE_PROCESSES" -ge 1 ]] ; then + scancode_processes="$SCANCODE_PROCESSES" + options="$options --processes $scancode_processes" fi echo "User provided license score: $SCANCODE_LICENSE_SCORE" -if [[ -n "$SCANCODE_LICENSE_SCORE" ]] -then - if [[ "$SCANCODE_LICENSE_SCORE" -le 100 && "$SCANCODE_LICENSE_SCORE" -ge 0 ]] - then - license_score="$SCANCODE_LICENSE_SCORE" - options="$options --license-score $license_score" - fi +if [[ -n "$SCANCODE_LICENSE_SCORE" ]] ; then + if [[ "$SCANCODE_LICENSE_SCORE" -le 100 && "$SCANCODE_LICENSE_SCORE" -ge 0 ]] ; then + license_score="$SCANCODE_LICENSE_SCORE" + options="$options --license-score $license_score" + fi fi echo "User provided timeout: $SCANCODE_TIMEOUT" -if [[ -n "$SCANCODE_TIMEOUT" ]] -then - if [[ "$SCANCODE_TIMEOUT" -ge 1 ]] - then - file_scan_timeout="$SCANCODE_TIMEOUT" - options="$options --timeout $file_scan_timeout" - fi +if [[ -n "$SCANCODE_TIMEOUT" ]] ; then + if [[ "$SCANCODE_TIMEOUT" -ge 1 ]] ; then + file_scan_timeout="$SCANCODE_TIMEOUT" + options="$options --timeout $file_scan_timeout" + fi fi given_output_format="$(echo "$SCANCODE_OUTPUT_FORMAT" | tr '[:upper:]' '[:lower:]')" @@ -136,50 +146,41 @@ echo "Possible output formats: $output_formats" echo "User provided output format: $given_output_format" # is given output format in output formats? -if [[ $output_formats =~ " $given_output_format " ]] -then - echo "User provided output format is in possible output formats." +if [[ $output_formats =~ " $given_output_format " ]] ; then + echo "User provided output format is in possible output formats." - if [[ "$given_output_format" != "spdx-json" ]] - then - convert_output_to_spdx_json="false" - output_format="--$given_output_format" - fi + if [[ "$given_output_format" != "spdx-json" ]] ; then + convert_output_to_spdx_json="false" + output_format="--$given_output_format" + fi fi -if [[ "$SCANCODE_SCAN_COPYRIGHT" == "true" ]] -then - options="$options --copyright" +if [[ "$SCANCODE_SCAN_COPYRIGHT" == "true" ]] ; then + options="$options --copyright" fi -if [[ "$SCANCODE_SCAN_LICENSE" == "true" ]] -then - options="$options --license" +if [[ "$SCANCODE_SCAN_DIAGNOSTICS" == "true" ]] ; then + options="$options --json $diagnostics_file" fi -if [[ "$SCANCODE_SCAN_PACKAGE" == "true" ]] -then - options="$options --package" +if [[ "$SCANCODE_SCAN_EMAIL" == "true" ]] ; then + options="$options --email" fi -if [[ "$SCANCODE_SCAN_EMAIL" == "true" ]] -then - options="$options --email" +if [[ "$SCANCODE_SCAN_INFO" == "true" ]] ; then + options="$options --info" fi -if [[ "$SCANCODE_SCAN_URL" == "true" ]] -then - options="$options --url" +if [[ "$SCANCODE_SCAN_LICENSE" == "true" ]] ; then + options="$options --license" fi -if [[ "$SCANCODE_SCAN_INFO" == "true" ]] -then - options="$options --info" +if [[ "$SCANCODE_SCAN_PACKAGE" == "true" ]] ; then + options="$options --package" fi -if [[ "$SCANCODE_SCAN_DIAGNOSTICS" == "true" ]] -then - options="$options --json $diagnostics_file" +if [[ "$SCANCODE_SCAN_URL" == "true" ]] ; then + options="$options --url" fi echo "" @@ -198,14 +199,11 @@ echo "" spdx_file="$PDS_JOB_RESULT_FILE.spdx" extractcode_enabled=$( echo "$EXTRACTCODE_ENABLED" | tr '[:upper:]' '[:lower:]' ) -if [[ "$extractcode_enabled" == "true" ]] -then +if [[ "$extractcode_enabled" == "true" ]] ; then echo "Running extractcode" - # `2>&1` -> redirect the verbose output from standard error to standard out extractcode --verbose "$extracted_folder" 2>&1 fi -# `2>&1` -> redirect the verbose output from standard error to standard out scancode $options --verbose --strip-root $output_format $spdx_file "$extracted_folder" 2>&1 echo "" @@ -214,15 +212,13 @@ echo "Preparing result" echo "----------------" echo "" -if [[ "$SCANCODE_SCAN_DIAGNOSTICS" == "true" ]] -then - mv "$diagnostics_file" "${PDS_JOB_USER_MESSAGES_FOLDER}/INFO_message_$(date +%Y-%m-%d_%H.%M.%S_%N).txt" +if [[ "$SCANCODE_SCAN_DIAGNOSTICS" == "true" ]] ; then + mv "$diagnostics_file" "${PDS_JOB_USER_MESSAGES_FOLDER}/INFO_message_$(date +%Y-%m-%d_%H.%M.%S_%N).txt" fi -if $convert_output_to_spdx_json -then - convert "$spdx_file" +if $convert_output_to_spdx_json ; then + convert "$spdx_file" else - echo "Moving SPDX file to $PDS_JOB_RESULT_FILE" - mv "$PDS_JOB_RESULT_FILE.spdx" "$PDS_JOB_RESULT_FILE" + echo "Moving SPDX file to $PDS_JOB_RESULT_FILE" + mv "$PDS_JOB_RESULT_FILE.spdx" "$PDS_JOB_RESULT_FILE" fi diff --git a/sechub-pds-solutions/scancode/helm/pds-scancode/Chart.yaml b/sechub-pds-solutions/scancode/helm/pds-scancode/Chart.yaml index 15627a69f1..1b299c24ee 100644 --- a/sechub-pds-solutions/scancode/helm/pds-scancode/Chart.yaml +++ b/sechub-pds-solutions/scancode/helm/pds-scancode/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-scancode description: SecHub PDS + Scancode-Toolkit as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Jeremias Eppler - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.1.0 +version: 1.2.0 diff --git a/sechub-pds-solutions/scancode/helm/pds-scancode/templates/deployment.yaml b/sechub-pds-solutions/scancode/helm/pds-scancode/templates/deployment.yaml index 94d094e255..c2ff0c1a0c 100644 --- a/sechub-pds-solutions/scancode/helm/pds-scancode/templates/deployment.yaml +++ b/sechub-pds-solutions/scancode/helm/pds-scancode/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/scancode/helm/pds-scancode/values.yaml b/sechub-pds-solutions/scancode/helm/pds-scancode/values.yaml index 7e55b9ba54..10372bf7a3 100644 --- a/sechub-pds-solutions/scancode/helm/pds-scancode/values.yaml +++ b/sechub-pds-solutions/scancode/helm/pds-scancode/values.yaml @@ -28,6 +28,17 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 1 + archive: + # Limiting parameters regarding the extraction of archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: size: "1Gi" diff --git a/sechub-pds-solutions/tern/10-create-image.sh b/sechub-pds-solutions/tern/10-create-image.sh index 7f4e327c0b..1789fd9856 100755 --- a/sechub-pds-solutions/tern/10-create-image.sh +++ b/sechub-pds-solutions/tern/10-create-image.sh @@ -44,10 +44,19 @@ fi BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" -if [[ ! -z "$TERN_VERSION" ]] ; then - echo ">> Tern version: $TERN_VERSION" - BUILD_ARGS="$BUILD_ARGS --build-arg TERN_VERSION=$TERN_VERSION" +if [[ -z "$TERN_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> Tern version: $TERN_VERSION" +BUILD_ARGS+=" --build-arg TERN_VERSION=$TERN_VERSION" + +if [[ -z "$SCANCODE_VERSION" ]] ; then + # source defaults + source ./env +fi +echo ">> Scancode version: $SCANCODE_VERSION" +BUILD_ARGS+=" --build-arg SCANCODE_VERSION=$SCANCODE_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/tern/docker-compose_pds_scancode_external-network.yaml b/sechub-pds-solutions/tern/docker-compose_pds_scancode_external-network.yaml index 40eeb5451d..423080342b 100644 --- a/sechub-pds-solutions/tern/docker-compose_pds_scancode_external-network.yaml +++ b/sechub-pds-solutions/tern/docker-compose_pds_scancode_external-network.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - TERN_VERSION=${TERN_VERSION} + - SCANCODE_VERSION=${SCANCODE_VERSION} context: docker/ dockerfile: Tern-Debian.dockerfile container_name: pds-tern diff --git a/sechub-pds-solutions/tern/docker-compose_pds_tern.yaml b/sechub-pds-solutions/tern/docker-compose_pds_tern.yaml index 69957f5e5d..2df53c930c 100644 --- a/sechub-pds-solutions/tern/docker-compose_pds_tern.yaml +++ b/sechub-pds-solutions/tern/docker-compose_pds_tern.yaml @@ -6,6 +6,8 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - TERN_VERSION=${TERN_VERSION} + - SCANCODE_VERSION=${SCANCODE_VERSION} context: docker/ dockerfile: Tern-Debian.dockerfile container_name: pds-tern diff --git a/sechub-pds-solutions/tern/docker/Tern-Debian.dockerfile b/sechub-pds-solutions/tern/docker/Tern-Debian.dockerfile index 1c5c27eda7..7bdac27823 100644 --- a/sechub-pds-solutions/tern/docker/Tern-Debian.dockerfile +++ b/sechub-pds-solutions/tern/docker/Tern-Debian.dockerfile @@ -4,21 +4,22 @@ ARG BASE_IMAGE FROM ${BASE_IMAGE} +# See: https://github.com/tern-tools/tern/releases +ARG TERN_VERSION +# See: https://github.com/nexB/scancode-toolkit/releases +ARG SCANCODE_VERSION + LABEL org.opencontainers.image.source="https://github.com/mercedes-benz/sechub" LABEL org.opencontainers.image.title="SecHub Tern+PDS Image" LABEL org.opencontainers.image.description="A container which combines Tern with the SecHub Product Delegation Server (PDS)" LABEL maintainer="SecHub FOSS Team" -ARG TERN_VERSION="2.12.1" -ARG SCANCODE_VERSION="32.0.4" - -# execute commands as root USER root RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get --quiet update && \ apt-get --quiet --assume-yes upgrade && \ - apt-get --quiet --assume-yes install attr bzip2 git jq libgomp1 libpopt0 libxml2-dev libxslt1-dev procps python3 python3-distutils python3-pip skopeo tar wget xz-utils zlib1g && \ + apt-get --quiet --assume-yes install attr bzip2 git jq libgomp1 libpopt0 libxml2-dev libxslt1-dev procps python3 python3-distutils python3-pip skopeo tar wget xz-utils zlib1g && \ apt-get --quiet --assume-yes clean # python-dev diff --git a/sechub-pds-solutions/tern/env b/sechub-pds-solutions/tern/env index d90811153f..0b3aa63047 100644 --- a/sechub-pds-solutions/tern/env +++ b/sechub-pds-solutions/tern/env @@ -5,4 +5,3 @@ TERN_VERSION="2.12.1" SCANCODE_VERSION="32.0.4" PDS_MAX_FILE_UPLOAD_BYTES=26214400000 - diff --git a/sechub-pds-solutions/xray/10-create-image.sh b/sechub-pds-solutions/xray/10-create-image.sh index 9a696a7fe3..41780f1aac 100755 --- a/sechub-pds-solutions/xray/10-create-image.sh +++ b/sechub-pds-solutions/xray/10-create-image.sh @@ -49,20 +49,22 @@ BUILD_ARGS="--build-arg BASE_IMAGE=$BASE_IMAGE" echo ">> Base image: $BASE_IMAGE" if [[ -z "$BUILD_TYPE" ]] ; then - BUILD_TYPE="$DEFAULT_BUILD_TYPE" + BUILD_TYPE="$DEFAULT_BUILD_TYPE" fi BUILD_ARGS+=" --build-arg BUILD_TYPE=$BUILD_TYPE" -echo ">> - Build type: $BUILD_TYPE" +echo ">> Build type: $BUILD_TYPE" if [[ ! -z "$BUILDER_BASE_IMAGE" ]] ; then - BUILD_ARGS+=" --build-arg BUILDER_BASE_IMAGE=$BUILDER_BASE_IMAGE" - echo ">> - Builder base image: $BUILDER_BASE_IMAGE" + BUILD_ARGS+=" --build-arg BUILDER_BASE_IMAGE=$BUILDER_BASE_IMAGE" + echo ">> Builder base image: $BUILDER_BASE_IMAGE" fi -if [[ ! -z "$XRAY_WRAPPER_VERSION" ]] ; then - echo ">> Xray version: $XRAY_WRAPPER_VERSION" - BUILD_ARGS="$BUILD_ARGS --build-arg XRAY_WRAPPER_VERSION=$XRAY_WRAPPER_VERSION" +if [[ -z "$XRAY_WRAPPER_VERSION" ]] ; then + # source defaults + source ./env fi +echo ">> Xray version: $XRAY_WRAPPER_VERSION" +BUILD_ARGS+=" --build-arg XRAY_WRAPPER_VERSION=$XRAY_WRAPPER_VERSION" # Use Docker BuildKit export BUILDKIT_PROGRESS=plain diff --git a/sechub-pds-solutions/xray/docker-compose_pds_xray.yaml b/sechub-pds-solutions/xray/docker-compose_pds_xray.yaml index 648a4ccab7..ece2f0c1bd 100644 --- a/sechub-pds-solutions/xray/docker-compose_pds_xray.yaml +++ b/sechub-pds-solutions/xray/docker-compose_pds_xray.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - XRAY_WRAPPER_VERSION=${XRAY_WRAPPER_VERSION} context: docker/ dockerfile: Xray-Debian.dockerfile container_name: pds-xray diff --git a/sechub-pds-solutions/xray/docker-compose_pds_xray_external-network.yaml b/sechub-pds-solutions/xray/docker-compose_pds_xray_external-network.yaml index bd1682ab85..385e56ef95 100644 --- a/sechub-pds-solutions/xray/docker-compose_pds_xray_external-network.yaml +++ b/sechub-pds-solutions/xray/docker-compose_pds_xray_external-network.yaml @@ -6,6 +6,7 @@ services: build: args: - BASE_IMAGE=${BASE_IMAGE} + - XRAY_WRAPPER_VERSION=${XRAY_WRAPPER_VERSION} context: docker/ dockerfile: Xray-Debian.dockerfile container_name: pds-xray diff --git a/sechub-pds-solutions/xray/docker/Xray-Debian.dockerfile b/sechub-pds-solutions/xray/docker/Xray-Debian.dockerfile index 570c8447ff..15978fab04 100644 --- a/sechub-pds-solutions/xray/docker/Xray-Debian.dockerfile +++ b/sechub-pds-solutions/xray/docker/Xray-Debian.dockerfile @@ -4,9 +4,10 @@ ARG BASE_IMAGE # Build Args +# See: See: https://github.com/mercedes-benz/sechub/releases +ARG XRAY_WRAPPER_VERSION # Build type can be "copy" or "download" -ARG BUILD_TYPE -ARG XRAY_WRAPPER_VERSION="1.0.0" +ARG BUILD_TYPE="download" # The base image of the builder ARG BUILDER_BASE_IMAGE="debian:12-slim" @@ -60,7 +61,7 @@ COPY copy/sechub-pds-wrapper-xray-$XRAY_WRAPPER_VERSION.jar "$ARTIFACT_FOLDER" # Builder #------------------- -FROM builder-${BUILD_TYPE} as builder +FROM builder-${BUILD_TYPE} AS builder RUN echo "build stage" @@ -70,14 +71,14 @@ RUN echo "build stage" FROM ${BASE_IMAGE} +ARG ARTIFACT_FOLDER +ARG XRAY_WRAPPER_VERSION + LABEL org.opencontainers.image.source="https://github.com/mercedes-benz/sechub" LABEL org.opencontainers.image.title="SecHub Xray+PDS Image" LABEL org.opencontainers.image.description="A container which combines Xray Wrapper with the SecHub Product Delegation Server (PDS)" LABEL maintainer="SecHub FOSS Team" -ARG ARTIFACT_FOLDER -ARG XRAY_WRAPPER_VERSION - USER root # Copy mock folder diff --git a/sechub-pds-solutions/xray/docker/pds-config.json b/sechub-pds-solutions/xray/docker/pds-config.json index d1eb2d8b3d..af5c4727f6 100644 --- a/sechub-pds-solutions/xray/docker/pds-config.json +++ b/sechub-pds-solutions/xray/docker/pds-config.json @@ -5,9 +5,15 @@ { "id": "PDS_XRAY", "path": "/pds/scripts/xray.sh", - "envWhitelist" : [ "TOOL_FOLDER" ], "scanType": "licenseScan", - "description": "Runs the JFrog Xray Wrapper to execute a scan on the artifactory." + "description": "Runs the JFrog Xray Wrapper to execute a scan on the artifactory.", + "envWhitelist": [ + "XRAY_ARTIFACTORY", + "XRAY_DOCKER_REGISTRY", + "XRAY_PASSWORD", + "XRAY_USERNAME", + "TOOL_FOLDER" + ] }, { "id": "PDS_XRAY_MOCK", diff --git a/sechub-pds-solutions/xray/docker/scripts/xray.sh b/sechub-pds-solutions/xray/docker/scripts/xray.sh index 91938c86b8..149ea283fb 100755 --- a/sechub-pds-solutions/xray/docker/scripts/xray.sh +++ b/sechub-pds-solutions/xray/docker/scripts/xray.sh @@ -1,6 +1,25 @@ #!/usr/bin/bash # SPDX-License-Identifier: MIT +function check_env_var_is_set { + local param="$1" + if [ -z "${!param}" ] ; then + echo "Mandatory environment variable $param is not set!" + failed=true + fi +} + +# Check if mandatory environment variables are set +MANDATORY_ENV_VARS="TOOL_FOLDER XRAY_ARTIFACTORY XRAY_DOCKER_REGISTRY XRAY_PASSWORD XRAY_USERNAME" +failed=false +for i in $MANDATORY_ENV_VARS ; do + check_env_var_is_set $i +done +if $failed ; then + echo "Please make sure that mandatory environment variables are passed to this script. Exiting." + exit 1 +fi + echo "" echo "---------" echo "PDS Setup" @@ -18,8 +37,7 @@ options="" check_valid_upload () { # count number of files/ folders uploaded and checks number of uploads greater equals 2 - if [ $(ls $UPLOAD_DIR | wc --lines) -ge 2 ] - then + if [ $(ls $UPLOAD_DIR | wc --lines) -ge 2 ] ; then echo "Error: more than one file was uploaded: $(ls $UPLOAD_DIR)" exit 1 fi @@ -28,9 +46,8 @@ check_valid_upload () { login_into_artifactory () { # login to JFROG artifactory LOGIN=$(skopeo login "$XRAY_ARTIFACTORY" --username "$XRAY_USERNAME" --password "$XRAY_PASSWORD" --authfile "$PDS_JOB_WORKSPACE_LOCATION/$SKOPEO_AUTH") - if [ "$LOGIN" != "Login Succeeded!" ] - then - echo "Error: Skopeo could not login to $XRAY_ARTIFACTORY with user $XRAY_USERNAME" + if [ "$LOGIN" != "Login Succeeded!" ] ; then + echo "Error: Skopeo could not login to \"$XRAY_ARTIFACTORY\" with user \"$XRAY_USERNAME\"" exit 1 fi } @@ -40,8 +57,8 @@ check_valid_upload login_into_artifactory cd "$PDS_JOB_WORKSPACE_LOCATION" -if [[ "$PDS_WRAPPER_REMOTE_DEBUGGING_ENABLED" = "true" ]]; then - options="$options -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000" +if [[ "$PDS_WRAPPER_REMOTE_DEBUGGING_ENABLED" = "true" ]] ; then + options="$options -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000" fi # Get docker archives from binary upload folder @@ -68,4 +85,4 @@ SHA256=$(jq '.Digest' "$PDS_JOB_WORKSPACE_LOCATION/inspect.json" | tr --delete \ java -jar $options "$TOOL_FOLDER/wrapper-xray.jar" "--name" "$IMAGE" "--checksum" "$SHA256" "--scantype" "docker" "--outputfile" "$PDS_JOB_RESULT_FILE" "--workspace" "$PDS_JOB_WORKSPACE_LOCATION" # SPDX report can be returned as followed - SPDX does not contain vulnerabilities -cp "$PDS_JOB_WORKSPACE_LOCATION/XrayArtifactoryReports/"*SPDX.json "$PDS_JOB_RESULT_FILE" \ No newline at end of file +cp "$PDS_JOB_WORKSPACE_LOCATION/XrayArtifactoryReports/"*SPDX.json "$PDS_JOB_RESULT_FILE" diff --git a/sechub-pds-solutions/xray/helm/pds-xray/Chart.yaml b/sechub-pds-solutions/xray/helm/pds-xray/Chart.yaml index d4f6f02cf2..8dd74b3c27 100644 --- a/sechub-pds-solutions/xray/helm/pds-xray/Chart.yaml +++ b/sechub-pds-solutions/xray/helm/pds-xray/Chart.yaml @@ -3,14 +3,10 @@ apiVersion: v2 name: pds-xray description: SecHub PDS + Xray wrapper as Helm chart for Kubernetes - +home: https://github.com/mercedes-benz/sechub type: application -maintainers: - - name: Laura Bottner - - name: Sven Dolderer - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. +# This is the chart version. +# This version number should be incremented each time you make changes to the chart and its templates. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.0 +version: 1.1.0 diff --git a/sechub-pds-solutions/xray/helm/pds-xray/templates/deployment.yaml b/sechub-pds-solutions/xray/helm/pds-xray/templates/deployment.yaml index 9a72a23307..844b9c0220 100644 --- a/sechub-pds-solutions/xray/helm/pds-xray/templates/deployment.yaml +++ b/sechub-pds-solutions/xray/helm/pds-xray/templates/deployment.yaml @@ -49,6 +49,14 @@ spec: value: "{{ .Values.pds.config.execute.queueMax }}" - name: PDS_CONFIG_EXECUTE_WORKER_THREAD_COUNT value: "{{ .Values.pds.config.execute.workerThreadCount }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_DIRECTORY_DEPTH + value: "{{ .Values.pds.archive.extraction.maxDirectoryDepth }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_ENTRIES + value: "{{ .Values.pds.archive.extraction.maxEntries }}" + - name: PDS_ARCHIVE_EXTRACTION_MAX_FILE_SIZE_UNCOMPRESSED + value: "{{ .Values.pds.archive.extraction.maxFileFizeUncompressed }}" + - name: PDS_ARCHIVE_EXTRACTION_TIMEOUT + value: "{{ .Values.pds.archive.extraction.timeout }}" - name: PDS_HEARTBEAT_LOGGING value: "{{ .Values.pds.heartbeatLogging }}" {{- if .Values.deploymentComment }} diff --git a/sechub-pds-solutions/xray/helm/pds-xray/values.yaml b/sechub-pds-solutions/xray/helm/pds-xray/values.yaml index 897157c058..27c6e4a625 100644 --- a/sechub-pds-solutions/xray/helm/pds-xray/values.yaml +++ b/sechub-pds-solutions/xray/helm/pds-xray/values.yaml @@ -28,8 +28,20 @@ pds: queueMax: 10 # Maximum number of jobs that are processed in parallel by PDS workerThreadCount: 1 + archive: + # Limiting parameters regarding the extraction of archives: + extraction: + # The maximal file size of the uncompressed archive (e.g.: 10KB or 10MB or 10GB) + maxFileFizeUncompressed: 1GB + # Defines how many entries the archive may have + maxEntries: 100000 + # The maximum directory depth for an entry inside the archive + maxDirectoryDepth: 100 + # The timeout of the archive extraction process + timeout: 1m volumes: workspace: + # Size of /workspace where the preparation is done size: "1Gi" heartbeatLogging: "true" logging: diff --git a/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/generator/PDSSolutionTestFilesGenerator.java b/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/generator/PDSSolutionTestFilesGenerator.java index ae7ca3b465..e013811980 100644 --- a/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/generator/PDSSolutionTestFilesGenerator.java +++ b/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/generator/PDSSolutionTestFilesGenerator.java @@ -110,7 +110,7 @@ private Path resolveWorkingDirectory(File workingDirectory) { } private void writeSecHubConfigurationToTempFolder() throws JSONConverterException, IOException { - writer.save(new File(targetFolder, "original-used-sechub-configfile.json"), JSONConverter.get().toJSON(config, true), false); + writer.writeTextToFile(new File(targetFolder, "original-used-sechub-configfile.json"), JSONConverter.get().toJSON(config, true), false); } @@ -120,7 +120,7 @@ private String writeReducedConfigFile() throws IOException { recucedSecHubConfigJson = JSONConverter.get().toJSON(reducedConfig, true); File reducedSecHubConfigFile = new File(targetFolder, "reducedSecHubJson_for_" + scanType.getId() + ".json"); - writer.save(reducedSecHubConfigFile, recucedSecHubConfigJson, true); + writer.writeTextToFile(reducedSecHubConfigFile, recucedSecHubConfigJson, true); return recucedSecHubConfigJson; } @@ -133,7 +133,7 @@ private void writePDSJobDataFile(String reducedSecHubConfigJson) throws IOExcept String pdsJobDataJson = JSONConverter.get().toJSON(data, true); File pdsJobDataFile = new File(targetFolder, "pdsJobData.json"); - writer.save(pdsJobDataFile, pdsJobDataJson, true); + writer.writeTextToFile(pdsJobDataFile, pdsJobDataJson, true); } private void ensureScanType(String wantedScanType) { @@ -145,7 +145,7 @@ private void ensureSecHubConfiguration(String pathToSecHubConfigFile) throws IOE if (!originConfigFile.exists()) { throw new FileNotFoundException("Sechub configuration file not found:" + originConfigFile.getAbsolutePath()); } - String json = reader.loadTextFile(originConfigFile); + String json = reader.readTextFromFile(originConfigFile); config = JSONConverter.get().fromJSON(SecHubConfigurationModel.class, json); } diff --git a/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncher.java b/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncher.java index 88d7b9b8cc..53d399a6cf 100644 --- a/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncher.java +++ b/sechub-pds-tools/src/main/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncher.java @@ -102,7 +102,7 @@ private void handleTestConfigurationFile(SystemTestCommand systemTestCommand, Sy if (!file.exists()) { throw new FileNotFoundException("Test configuration file does not exist:" + file.getAbsolutePath()); } - String configAsText = reader.loadTextFile(file); + String configAsText = reader.readTextFromFile(file); SystemTestConfiguration testConfiguration = JSONConverter.get().fromJSON(SystemTestConfiguration.class, configAsText); builder.testConfiguration(testConfiguration); diff --git a/sechub-pds-tools/src/test/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncherTest.java b/sechub-pds-tools/src/test/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncherTest.java index 3acb94e3e6..e8eabff31f 100644 --- a/sechub-pds-tools/src/test/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncherTest.java +++ b/sechub-pds-tools/src/test/java/com/mercedesbenz/sechub/pds/tools/systemtest/SystemTestLauncherTest.java @@ -159,7 +159,7 @@ void system_test_parameters_are_build_from_command_as_expected__no_tests_to_run_ private String loadJsonAsPrettyPrinted(String path) throws IOException { TextFileReader reader = new TextFileReader(); - String json = reader.loadTextFile(new File(path)); + String json = reader.readTextFromFile(new File(path)); SystemTestConfiguration expectedConfiguration = JSONConverter.get().fromJSON(SystemTestConfiguration.class, json); String expectedPrettyJson = JSONConverter.get().toJSON(expectedConfiguration, true); return expectedPrettyJson; diff --git a/sechub-pds/build.gradle b/sechub-pds/build.gradle index 71f9ceeded..db6a44ab48 100644 --- a/sechub-pds/build.gradle +++ b/sechub-pds/build.gradle @@ -22,9 +22,6 @@ dependencies { implementation(library.apache_commons_io) implementation(library.apache_commons_fileupload2_core) implementation(library.apache_commons_fileupload2_jakarta) - - - api project(':sechub-pds-core') diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSEncryptionConfiguration.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSPasswordEncoderConfiguration.java similarity index 91% rename from sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSEncryptionConfiguration.java rename to sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSPasswordEncoderConfiguration.java index 2368c16751..c0021837d3 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSEncryptionConfiguration.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSPasswordEncoderConfiguration.java @@ -7,7 +7,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; @Configuration -public class PDSEncryptionConfiguration { +public class PDSPasswordEncoderConfiguration { @Bean public PasswordEncoder passwordEncoder() { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSPojoFactory.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSPojoFactory.java index 626062db8d..c72a5cff05 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSPojoFactory.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSPojoFactory.java @@ -10,6 +10,8 @@ import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironment; import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironmentVariableSupport; import com.mercedesbenz.sechub.commons.core.security.CheckSumSupport; +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; import com.mercedesbenz.sechub.commons.model.CodeScanPathCollector; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelSupport; import com.mercedesbenz.sechub.commons.model.SecHubDataConfigurationTypeListParser; @@ -26,6 +28,16 @@ @Component public class PDSPojoFactory { + @Bean + EncryptionSupport createEncryptionSupport() { + return new EncryptionSupport(); + } + + @Bean + PersistentCipherFactory createPersistentCipherFactory() { + return new PersistentCipherFactory(); + } + @Bean SecHubDataConfigurationTypeListParser createTypeListParser() { return new SecHubDataConfigurationTypeListParser(); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsed.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsed.java index 863164a360..59ad6f1222 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsed.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsed.java @@ -13,6 +13,8 @@ import com.mercedesbenz.sechub.commons.core.environment.SecureEnvironmentVariableKeyValueRegistry; import com.mercedesbenz.sechub.commons.core.environment.SecureEnvironmentVariableKeyValueRegistry.EnvironmentVariableKeyValueEntry; import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironmentVariableSupport; +import com.mercedesbenz.sechub.pds.encryption.PDSCipherAlgorithm; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionConfiguration; import com.mercedesbenz.sechub.pds.security.PDSSecurityConfiguration; import com.mercedesbenz.sechub.pds.storage.PDSS3PropertiesSetup; import com.mercedesbenz.sechub.pds.storage.PDSSharedVolumePropertiesSetup; @@ -38,6 +40,9 @@ public class PDSStartupAssertEnvironmentVariablesUsed { @Autowired PDSSecurityConfiguration securityConfiguration; + @Autowired + PDSEncryptionConfiguration encryptionConfiguration; + @Autowired Environment environment; @@ -90,10 +95,14 @@ public SecureEnvironmentVariableKeyValueRegistry createRegistryForOnlyAllowedAsE if (securityConfiguration == null) { securityConfiguration = PDSSecurityConfiguration.create("test-user", "test-user-token", "test-admin", "test-admintoken"); } + if (encryptionConfiguration == null) { + encryptionConfiguration = PDSEncryptionConfiguration.create(PDSCipherAlgorithm.NONE, null); + } } s3Setup.registerOnlyAllowedAsEnvironmentVariables(sensitiveDataRegistry); sharedVolumeSetup.registerOnlyAllowedAsEnvironmentVariables(sensitiveDataRegistry); securityConfiguration.registerOnlyAllowedAsEnvironmentVariables(sensitiveDataRegistry); + encryptionConfiguration.registerOnlyAllowedAsEnvironmentVariables(sensitiveDataRegistry); // some additional parts which shall only be available as environment variables // - h2 databases allow no setup here, so not mandatory diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupDaysCalculator.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupDaysCalculator.java index fd45ea425b..51c8d6a740 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupDaysCalculator.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupDaysCalculator.java @@ -15,7 +15,7 @@ public class PDSAutoCleanupDaysCalculator { /** * Calculates cleanup time in days * - * @param config + * @param configuration * @return cleanup time in days */ public long calculateCleanupTimeInDays(PDSAutoCleanupConfig config) { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupService.java index b9db0ce23f..4d09506283 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupService.java @@ -10,7 +10,7 @@ import com.mercedesbenz.sechub.pds.config.PDSConfigService; import com.mercedesbenz.sechub.pds.job.PDSJobRepository; -import com.mercedesbenz.sechub.pds.time.TimeCalculationService; +import com.mercedesbenz.sechub.pds.time.PDSTimeCalculationService; import com.mercedesbenz.sechub.pds.usecase.PDSStep; import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemExecutesAutoCleanup; @@ -23,7 +23,7 @@ public class PDSAutoCleanupService { PDSAutoCleanupResultInspector inspector; @Autowired - TimeCalculationService timeCalculationService; + PDSTimeCalculationService PDSTimeCalculationService; @Autowired PDSConfigService configService; @@ -44,7 +44,7 @@ public void cleanup() { LOG.trace("Cancel administration auto cleanup because disabled."); return; } - LocalDateTime cleanTimeStamp = timeCalculationService.calculateNowMinusDays(days); + LocalDateTime cleanTimeStamp = PDSTimeCalculationService.calculateNowMinusDays(days); /* delete */ int amount = jobRepository.deleteJobOlderThan(cleanTimeStamp); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSConfigService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSConfigService.java index fa33d7e72a..178d495cb8 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSConfigService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSConfigService.java @@ -38,7 +38,7 @@ public class PDSConfigService { PDSAutoCleanupDaysCalculator calculator; @UseCaseAdminUpdatesAutoCleanupConfiguration(@PDSStep(number = 2, next = { 3, 4, - 5 }, name = "Updates auto cleanup config", description = "Updates auto cleanup configuration as JSON in database")) + 5 }, name = "Updates auto cleanup configuration", description = "Updates auto cleanup configuration as JSON in database")) public void updateAutoCleanupConfiguration(PDSAutoCleanupConfig configuration) { Assert.notNull(configuration, "configuration may not be null"); @@ -72,7 +72,7 @@ public void updateAutoCleanupInDays(long autoCleanupInDays) { transactionService.saveConfigInOwnTransaction(config); } - @UseCaseAdminFetchesAutoCleanupConfiguration(@PDSStep(number = 2, name = "Fetches auto cleanup config", description = "Fetches auto cleanup configuration from database")) + @UseCaseAdminFetchesAutoCleanupConfiguration(@PDSStep(number = 2, name = "Fetches auto cleanup configuration", description = "Fetches auto cleanup configuration from database")) public PDSAutoCleanupConfig fetchAutoCleanupConfiguration() { String cleanupConfigJson = getOrCreateConfig().autoCleanupConfiguration; PDSAutoCleanupConfig cleanupConfig = null; diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSServerConfigurationService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSServerConfigurationService.java index 9ae837d6ee..f38bdaad38 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSServerConfigurationService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSServerConfigurationService.java @@ -32,7 +32,7 @@ public class PDSServerConfigurationService { private static final Logger LOG = LoggerFactory.getLogger(PDSServerConfigurationService.class); - private static final String DEFAULT_PATH = "./pds-config.json"; + private static final String DEFAULT_PATH = "./pds-configuration.json"; private static final int defaultMinutesToWaitForProduct = PDSDefaultParameterValueConstants.DEFAULT_MINUTES_TO_WAIT_FOR_PRODUCT; private static final int defaultMaxConfigurableMinutesToWaitForProduct = PDSDefaultParameterValueConstants.MAXIMUM_CONFIGURABLE_TIME_TO_WAIT_FOR_PRODUCT_IN_MINUTES; @@ -86,10 +86,10 @@ protected void postConstruct() { } } catch (PDSJSONConverterException | IOException e) { - LOG.error("no configuration available, because cannot read config file", e); + LOG.error("no configuration available, because cannot read configuration file", e); } } else { - LOG.error("No config file found at {} !", file.getAbsolutePath()); + LOG.error("No configuration file found at {} !", file.getAbsolutePath()); } if (configuration == null) { LOG.error( diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSSummaryLogService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSSummaryLogService.java index 4dce6b864e..676124600e 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSSummaryLogService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/config/PDSSummaryLogService.java @@ -17,6 +17,7 @@ import com.mercedesbenz.sechub.pds.commons.core.config.PDSProductParameterSetup; import com.mercedesbenz.sechub.pds.commons.core.config.PDSProductSetup; import com.mercedesbenz.sechub.pds.commons.core.config.PDSServerConfiguration; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionConfiguration; @Service public class PDSSummaryLogService { @@ -26,18 +27,22 @@ public class PDSSummaryLogService { @Autowired PDSServerConfigurationService configurationService; + @Autowired + PDSEncryptionConfiguration encryptionConfigurationService; + @EventListener(ApplicationReadyEvent.class) void applicationReady() { PDSServerConfiguration configuration = configurationService.getServerConfiguration(); StringBuilder summary = new StringBuilder(); summary.append("PDS has been started successfully.\n**************************\n Summary\n**************************"); - summary.append("\n- config file used: ").append(configurationService.pathToConfigFile); + summary.append("\n- configuration file used: ").append(configurationService.pathToConfigFile); summary.append("\n- server id: ").append(configuration.getServerId()); summary.append("\n- system wide minutes to wait for product: ").append(configurationService.getMinutesToWaitForProduct()); summary.append("\n- minimum configurable minutes to wait for product: ").append(configurationService.getMinimumConfigurableMinutesToWaitForProduct()); summary.append("\n- maximum configurable minutes to wait for product: ").append(configurationService.getMaximumConfigurableMinutesToWaitForProduct()); + summary.append("\n- encryption algorithm used: ").append(encryptionConfigurationService.getAlgorithm()); List products = configuration.getProducts(); summary.append("\n- Available products: ").append(products.size()); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSCipherAlgorithm.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSCipherAlgorithm.java new file mode 100644 index 0000000000..6a913813e9 --- /dev/null +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSCipherAlgorithm.java @@ -0,0 +1,23 @@ +package com.mercedesbenz.sechub.pds.encryption; + +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; + +public enum PDSCipherAlgorithm { + + NONE(PersistentCipherType.NONE), + + AES_GCM_SIV_128(PersistentCipherType.AES_GCM_SIV_128), + + AES_GCM_SIV_256(PersistentCipherType.AES_GCM_SIV_256),; + + private PersistentCipherType type; + + private PDSCipherAlgorithm(PersistentCipherType type) { + this.type = type; + } + + public PersistentCipherType getType() { + return type; + } + +} diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionConfiguration.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionConfiguration.java new file mode 100644 index 0000000000..61a221302f --- /dev/null +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionConfiguration.java @@ -0,0 +1,111 @@ +package com.mercedesbenz.sechub.pds.encryption; + +import static com.mercedesbenz.sechub.pds.usecase.PDSDocumentationScopeConstants.*; + +import java.util.List; + +import javax.crypto.SealedObject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.core.environment.SecureEnvironmentVariableKeyValueRegistry; +import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; +import com.mercedesbenz.sechub.pds.PDSMustBeDocumented; +import com.mercedesbenz.sechub.pds.commons.core.PDSProfiles; + +import jakarta.annotation.PostConstruct; + +@Component +public class PDSEncryptionConfiguration { + + private static final Logger LOG = LoggerFactory.getLogger(PDSEncryptionConfiguration.class); + + private static final String KEY_ROOT_PATH = "pds.encryption."; + private static final String KEY_SECRET_KEY = KEY_ROOT_PATH + "secret-key"; + private static final String KEY_ALGORITHM = KEY_ROOT_PATH + "algorithm"; + private static final String ENV_ALGORITHM = KEY_ALGORITHM.toUpperCase().replace('.', '_'); + + @PDSMustBeDocumented(value = "The secret key used for encryption. It must be base64 encoded, otherwise it is not accepted.", scope = SCOPE_ENCRYPTION, secret = true) + @Value("${" + KEY_SECRET_KEY + ":}") + String secretKeyAsString; + + @PDSMustBeDocumented(value = "The encryption type. Allowed values are: NONE, AES_GCM_SIV_128 or AES_GCM_SIV_256", scope = SCOPE_ENCRYPTION) + @Value("${" + KEY_ALGORITHM + ":NONE}") + String algorithmAsString; + + @Autowired + private Environment springEnvironment; + + private PDSCipherAlgorithm algorithm; + private SealedObject sealedSecretKey; + + @PostConstruct + void init() throws PDSEncryptionException { + boolean secretKeyWasEmpty = secretKeyAsString == null || secretKeyAsString.isBlank(); + sealedSecretKey = CryptoAccess.CRYPTO_STRING.seal(secretKeyAsString); + secretKeyAsString = null; // reset + + try { + algorithm = PDSCipherAlgorithm.valueOf(algorithmAsString.toUpperCase()); + } catch (RuntimeException e) { + throw new PDSEncryptionException("Algorithm '" + algorithmAsString + "' not supported. Please set " + ENV_ALGORITHM + + " to one of the following values: " + List.of(PDSCipherAlgorithm.values()), e); + } + handleSecretKeyEmptyOrNot(secretKeyWasEmpty); + } + + private void handleSecretKeyEmptyOrNot(boolean secretKeyWasEmpty) throws PDSEncryptionException { + switch (algorithm) { + case NONE: + if (!secretKeyWasEmpty) { + LOG.warn("You used a non empty secret key for cipher algorithm {}. Did you forget to change the algorithm type?", algorithm); + } + break; + default: + if (secretKeyWasEmpty) { + throw new PDSEncryptionException("The cipher algorithm " + algorithm + " does not allow an empty secret key!"); + } + } + } + + public byte[] getSecretKeyBytes() { + return CryptoAccess.CRYPTO_STRING.unseal(sealedSecretKey).getBytes(); + } + + public PDSCipherAlgorithm getAlgorithm() { + return algorithm; + } + + public void registerOnlyAllowedAsEnvironmentVariables(SecureEnvironmentVariableKeyValueRegistry registry) { + if (springEnvironment != null && springEnvironment.matchesProfiles(PDSProfiles.INTEGRATIONTEST)) { + /* + * on integration test we accept credentials from configuration file or as + * system properties - not marked as sensitive + */ + return; + } + registry.register(registry.newEntry().key(KEY_ALGORITHM).notNullValue(algorithmAsString)); + registry.register(registry.newEntry().key(KEY_SECRET_KEY).nullableValue(CryptoAccess.CRYPTO_STRING.unseal(sealedSecretKey))); + + } + + /** + * Creates a encryption configuration which can be used by AsciiDoc generator + * + * @param algorithm algorithm to use for encryption + * @param secretKeyAsString secret key as plain string + * @return configuration, never null + */ + public static PDSEncryptionConfiguration create(PDSCipherAlgorithm algorithm, String secretKeyAsString) { + PDSEncryptionConfiguration config = new PDSEncryptionConfiguration(); + config.algorithmAsString = algorithm.name(); + config.secretKeyAsString = secretKeyAsString; + return config; + } + +} diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionException.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionException.java new file mode 100644 index 0000000000..597a2c05d5 --- /dev/null +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionException.java @@ -0,0 +1,14 @@ +package com.mercedesbenz.sechub.pds.encryption; + +public class PDSEncryptionException extends Exception { + + private static final long serialVersionUID = 1L; + + public PDSEncryptionException(String message) { + super(message); + } + + public PDSEncryptionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionService.java new file mode 100644 index 0000000000..a3b711433e --- /dev/null +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionService.java @@ -0,0 +1,60 @@ +package com.mercedesbenz.sechub.pds.encryption; + +import java.util.Base64; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.encryption.DefaultSecretKeyProvider; +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; + +import jakarta.annotation.PostConstruct; + +@Service +public class PDSEncryptionService { + + @Autowired + EncryptionSupport encryptionSupport; + + @Autowired + PersistentCipherFactory cipherFactory; + + @Autowired + PDSEncryptionConfiguration configuration; + + PersistentCipher cipher; + + @PostConstruct + void init() { + + PDSCipherAlgorithm algorithm = configuration.getAlgorithm(); + if (algorithm == null) { + throw new IllegalStateException("No cipher algorithm defined!"); + } + SecretKeyProvider secretKeyProvider = null; + switch (algorithm) { + case NONE: + break; + default: + byte[] base64decoded = Base64.getDecoder().decode(configuration.getSecretKeyBytes()); + secretKeyProvider = new DefaultSecretKeyProvider(base64decoded, algorithm.getType()); + break; + + } + cipher = cipherFactory.createCipher(secretKeyProvider, algorithm.getType()); + } + + public String decryptString(byte[] encryptedData, InitializationVector initialVector) { + return encryptionSupport.decryptString(encryptedData, cipher, initialVector); + } + + public EncryptionResult encryptString(String plainText) { + return encryptionSupport.encryptString(plainText, cipher); + } + +} diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSApplyFutureExecutionResultToJobService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSApplyFutureExecutionResultToJobService.java new file mode 100644 index 0000000000..b2e9858aad --- /dev/null +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSApplyFutureExecutionResultToJobService.java @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.pds.execution; + +import java.time.LocalDateTime; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; +import com.mercedesbenz.sechub.pds.job.PDSJob; +import com.mercedesbenz.sechub.pds.job.PDSJobRepository; + +@Service +public class PDSApplyFutureExecutionResultToJobService { + + private static final Logger LOG = LoggerFactory.getLogger(PDSApplyFutureExecutionResultToJobService.class); + + @Autowired + PDSJobRepository repository; + + /** + * Applies given future of an execution result to the given PDS job. + * + * @param future future result + * @param job pds + */ + public void applyResultToJob(Future future, PDSJob job) { + // we use this moment of time for all, currently the easiest and central way + job.setEnded(LocalDateTime.now()); + + UUID jobUUID = job.getUUID(); + if (future.isCancelled()) { + job.setState(PDSJobStatusState.CANCELED); + } else { + PDSExecutionResult callResult; + try { + callResult = future.get(); + LOG.debug("Fetch job result from future, pds job uuid={}, state={}", jobUUID, job.getState()); + job.setResult(callResult.getResult()); + + if (callResult.isCanceled()) { + job.setState(PDSJobStatusState.CANCELED); + } else if (callResult.isFailed()) { + job.setState(PDSJobStatusState.FAILED); + if (callResult.isEncryptionFailure()) { + job.setEncryptionOutOfSync(true); + } + } else { + job.setState(PDSJobStatusState.DONE); + } + + } catch (InterruptedException e) { + LOG.error("Job with uuid:{} was interrupted", jobUUID, e); + + job.setState(PDSJobStatusState.FAILED); + job.setResult("Job interrupted"); + } catch (ExecutionException e) { + LOG.error("Job with uuid:{} failed in execution", jobUUID, e); + + job.setState(PDSJobStatusState.FAILED); + job.setResult("Job execution failed"); + } + LOG.debug("Handled job result and state job uuid={}, state={}", jobUUID, job.getState()); + } + repository.save(job); + LOG.debug("Stored job pds uuid={}, state={}", jobUUID, job.getState()); + } + +} diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java index 738fb7633e..6dfb1085f2 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.pds.execution; +import static com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants.*; import static com.mercedesbenz.sechub.pds.util.PDSAssert.*; import java.io.File; @@ -25,7 +26,7 @@ import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants; import com.mercedesbenz.sechub.commons.pds.ProcessAdapter; import com.mercedesbenz.sechub.pds.PDSLogConstants; -import com.mercedesbenz.sechub.pds.commons.core.PDSJSONConverterException; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionException; import com.mercedesbenz.sechub.pds.job.JobConfigurationData; import com.mercedesbenz.sechub.pds.job.PDSCheckJobStatusService; import com.mercedesbenz.sechub.pds.job.PDSGetJobStreamService; @@ -101,9 +102,8 @@ public PDSExecutionResult call() throws Exception { MDC.put(PDSLogConstants.MDC_PDS_JOB_UUID, Objects.toString(pdsJobUUID)); getJobTransactionService().markJobAsRunningInOwnTransaction(pdsJobUUID); - - JobConfigurationData data = getJobTransactionService().getJobConfigurationData(pdsJobUUID); - config = PDSJobConfiguration.fromJSON(data.getJobConfigurationJson()); + JobConfigurationData data = getJobTransactionService().getJobConfigurationDataOrFail(pdsJobUUID); + config = data.getJobConfiguration(); MDC.put(PDSLogConstants.MDC_SECHUB_JOB_UUID, Objects.toString(config.getSechubJobUUID())); @@ -127,15 +127,18 @@ public PDSExecutionResult call() throws Exception { LOG.info("Workspace not prepared enough for launcher script, so skipping execution of product: {} for pds job: {}", config.getProductId(), pdsJobUUID); - result.exitCode = 0; + result.setExitCode(0); } } catch (Exception e) { LOG.error("Execution of job uuid:{} failed", pdsJobUUID, e); - result.failed = true; - result.result = "Execution of job uuid:" + pdsJobUUID + " failed. Please look into PDS logs for details and search for former string."; + result.setFailed(true); + if (e instanceof PDSEncryptionException) { + result.setEncryptionFailure(true); + } + result.setResult("Execution of job uuid:" + pdsJobUUID + " failed. Please look into PDS logs for details and search for former string."); } finally { cleanUpWorkspace(pdsJobUUID, config); @@ -147,15 +150,15 @@ public PDSExecutionResult call() throws Exception { * handle always exit code. Everything having an exit code != 0 is handled as an * error */ - if (result.exitCode != 0) { - result.failed = true; + if (result.getExitCode() != 0) { + result.setFailed(true); } - result.canceled = cancelOperationsHasBeenStarted; + result.setCanceled(cancelOperationsHasBeenStarted); - LOG.info("Finished execution of job {} with exitCode={}, failed={}, cancelOperationsHasBeenStarted={}", pdsJobUUID, result.exitCode, result.failed, - cancelOperationsHasBeenStarted); + LOG.info("Finished execution of job {} with exitCode={}, failed={}, cancelOperationsHasBeenStarted={}", pdsJobUUID, result.getExitCode(), + result.isFailed(), cancelOperationsHasBeenStarted); - if (result.failed) { + if (result.isFailed()) { PDSGetJobStreamService pdsGetJobStreamService = serviceCollection.getPdsGetJobStreamService(); String truncatedErrorStream = pdsGetJobStreamService.getJobErrorStreamTruncated(pdsJobUUID); String truncatedOutputStream = pdsGetJobStreamService.getJobOutputStreamTruncated(pdsJobUUID); @@ -175,7 +178,7 @@ Job output stream (last %s chars): ------------------------------------ %s - """.formatted(pdsJobUUID, productPath, result.exitCode, lastChars, truncatedErrorStream, lastChars, truncatedOutputStream); + """.formatted(pdsJobUUID, productPath, result.getExitCode(), lastChars, truncatedErrorStream, lastChars, truncatedOutputStream); LOG.error(message); } @@ -228,10 +231,10 @@ void waitForProcessEndAndGetResultByFiles(PDSExecutionResult result, UUID jobUUI if (exitDoneInTime) { LOG.debug("Job execution {} done - product id:{}.", jobUUID, config.getProductId()); - result.failed = false; - result.exitCode = process.exitValue(); + result.setFailed(false); + result.setExitCode(process.exitValue()); - LOG.debug("Process of PDS job with uuid: {} ended in time with exit code: {} after {} ms - for product with id: {}", jobUUID, result.exitCode, + LOG.debug("Process of PDS job with uuid: {} ended in time with exit code: {} after {} ms - for product with id: {}", jobUUID, result.getExitCode(), timeElapsedInMilliseconds, config.getProductId()); storeResultFileOrCreateShrinkedProblemDataInstead(result, jobUUID); @@ -240,9 +243,9 @@ void waitForProcessEndAndGetResultByFiles(PDSExecutionResult result, UUID jobUUI LOG.error("Process did not end in time for PDS job with uuid: {} for product id: {}. Waited {} minutes.", jobUUID, config.getProductId(), minutesToWaitForResult); - result.failed = true; - result.result = "Product time out."; - result.exitCode = 1; + result.setFailed(true); + result.setResult("Product time out."); + result.setExitCode(1); prepareForCancel(true); } @@ -263,12 +266,12 @@ private void storeResultFileOrCreateShrinkedProblemDataInstead(PDSExecutionResul if (file.exists()) { LOG.debug("Result file found - will read data and set as result"); - result.result = FileUtils.readFileToString(file, encoding); + result.setResult(FileUtils.readFileToString(file, encoding)); } else { LOG.debug("Result file NOT found - will append output and error streams as result"); - result.failed = true; - result.result = "Result file not found at " + file.getAbsolutePath(); + result.setFailed(true); + result.setResult("Result file not found at " + file.getAbsolutePath()); int max = MAXIMUM_START_TRUNCATE_CHARS; @@ -299,7 +302,7 @@ private String appendErrorStreamToResultAndReturnShrinkedVariant(PDSExecutionRes File systemErrorFile = getWorkspaceService().getSystemErrorFile(jobUUID); if (systemErrorFile.exists()) { String error = FileUtils.readFileToString(systemErrorFile, encoding); - result.result += "\nErrors:\n" + error; + result.setResult(result.getResult() + "\nErrors:\n" + error); shrinkedErrorStream = shrinkTo(error, max); } return shrinkedErrorStream; @@ -311,7 +314,7 @@ private String appendOutputStreamToResultAndReturnShrinkedVariant(PDSExecutionRe if (systemOutFile.exists()) { String output = FileUtils.readFileToString(systemOutFile, encoding); - result.result += "\nOutput:\n" + output; + result.setResult(result.getResult() + "\nOutput:\n" + output); shrinkedOutputStream = shrinkTo(output, max); } return shrinkedOutputStream; @@ -512,15 +515,21 @@ boolean prepareForCancel(boolean mayInterruptIfRunning) { } catch (RuntimeException e) { return false; } finally { - - JobConfigurationData data = getJobTransactionService().getJobConfigurationData(pdsJobUUID); + PDSJobConfiguration jobConfiguration = null; try { - PDSJobConfiguration config = PDSJobConfiguration.fromJSON(data.getJobConfigurationJson()); - cleanUpWorkspace(pdsJobUUID, config); - } catch (PDSJSONConverterException e) { - LOG.error("Was not able fetch job config for {} - workspace clean only workspace files", pdsJobUUID, e); + JobConfigurationData data = getJobTransactionService().getJobConfigurationDataOrFail(pdsJobUUID); + jobConfiguration = data.getJobConfiguration(); + + } catch (PDSEncryptionException e) { + LOG.warn("Was not able to decrypt configuration of PDS job: {}", pdsJobUUID, e); + + LOG.info("Create fallback configuration, asuming sechub storage reuse is enabled (SecHub does storage cleanup)"); + jobConfiguration = new PDSJobConfiguration(); + PDSExecutionParameterEntry resueSecHubConfigParameter = new PDSExecutionParameterEntry(PARAM_KEY_PDS_CONFIG_USE_SECHUB_STORAGE, "true"); + jobConfiguration.getParameters().add(resueSecHubConfigParameter); } + cleanUpWorkspace(pdsJobUUID, jobConfiguration); } diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java index 825f3c4064..27fdcaa7eb 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java @@ -49,10 +49,10 @@ public void initProcessBuilderEnvironmentMap(UUID pdsJobUUID, PDSJobConfiguratio throw new IllegalArgumentException("pds job uuid may not be null!"); } if (config == null) { - throw new IllegalArgumentException("pds job config may not be null!"); + throw new IllegalArgumentException("pds job configuration may not be null!"); } if (builder == null) { - throw new IllegalArgumentException("pds job config may not be null!"); + throw new IllegalArgumentException("pds job configuration may not be null!"); } String productId = config.getProductId(); if (productId == null) { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionResult.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionResult.java index efbbacd133..3d5aa29cff 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionResult.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionResult.java @@ -3,9 +3,51 @@ public class PDSExecutionResult { - int exitCode; - boolean failed; + private int exitCode; + private boolean failed; + + private String result; + private boolean canceled; + private boolean encryptionFailure; + + public int getExitCode() { + return exitCode; + } + + public void setExitCode(int exitCode) { + this.exitCode = exitCode; + } + + public boolean isFailed() { + return failed; + } + + public void setFailed(boolean failed) { + this.failed = failed; + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + public boolean isCanceled() { + return canceled; + } + + public void setCanceled(boolean canceled) { + this.canceled = canceled; + } + + public boolean isEncryptionFailure() { + return encryptionFailure; + } + + public void setEncryptionFailure(boolean encryptionFailure) { + this.encryptionFailure = encryptionFailure; + } - String result; - boolean canceled; } diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java index 432f11d797..c58b489075 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java @@ -3,7 +3,6 @@ import static com.mercedesbenz.sechub.pds.util.PDSAssert.*; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; @@ -14,7 +13,6 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -94,6 +92,9 @@ public class PDSExecutionService { @Autowired PDSWorkspaceService workspaceService; + @Autowired + PDSApplyFutureExecutionResultToJobService applyService; + @PostConstruct protected void postConstruct() { workers = Executors.newFixedThreadPool(workerThreadCount); @@ -318,43 +319,10 @@ private boolean isFutureDoneAndChangesToDatabaseCanBeApplied(Entry collect(File folder) { for (File file : folder.listFiles()) { try { - String text = reader.loadTextFile(file); + String text = reader.readTextFromFile(file); SecHubMessageType type = SecHubMessageType.INFO; String fileName = file.getName().toUpperCase(); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/JobConfigurationData.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/JobConfigurationData.java index f69da45c1e..efffca9179 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/JobConfigurationData.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/JobConfigurationData.java @@ -2,14 +2,19 @@ package com.mercedesbenz.sechub.pds.job; public class JobConfigurationData { - String jobConfigurationJson; - String metaData; + private PDSJobConfiguration jobConfigurationJson; + private String metaData; + + JobConfigurationData(PDSJobConfiguration jobConfigurationJson, String metaData) { + this.jobConfigurationJson = jobConfigurationJson; + this.metaData = metaData; + } public String getMetaData() { return metaData; } - public String getJobConfigurationJson() { + public PDSJobConfiguration getJobConfiguration() { return jobConfigurationJson; } } \ No newline at end of file diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobService.java index 56454c8081..7f9a14d92c 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobService.java @@ -8,10 +8,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; import com.mercedesbenz.sechub.pds.PDSNotAcceptableException; import com.mercedesbenz.sechub.pds.commons.core.PDSJSONConverterException; import com.mercedesbenz.sechub.pds.config.PDSServerConfigurationService; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionService; import com.mercedesbenz.sechub.pds.security.PDSRoleConstants; import com.mercedesbenz.sechub.pds.security.PDSUserContextService; import com.mercedesbenz.sechub.pds.usecase.PDSStep; @@ -37,6 +39,9 @@ public class PDSCreateJobService { @Autowired PDSServerConfigurationService serverConfigurationService; + @Autowired + PDSEncryptionService encryptionService; + @UseCaseUserCreatesJob(@PDSStep(name = "service call", description = "job will be created, serverId will be used to store new job", number = 2)) public PDSJobCreateResult createJob(PDSJobConfiguration configuration) { @@ -50,7 +55,10 @@ public PDSJobCreateResult createJob(PDSJobConfiguration configuration) { job.setServerId(serverConfigurationService.getServerId()); try { - job.jsonConfiguration = configuration.toJSON(); + EncryptionResult encryptionResult = encryptionService.encryptString(configuration.toJSON()); + job.encryptionInitialVectorData = encryptionResult.getInitialVector().getInitializationBytes(); + job.encryptedConfiguration = encryptionResult.getEncryptedData(); + } catch (PDSJSONConverterException e) { throw new PDSNotAcceptableException("Configuration conversion failure:" + e.getMessage()); } diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSFileUploadJobService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSFileUploadJobService.java index 9294552bc2..2874cdcf58 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSFileUploadJobService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSFileUploadJobService.java @@ -113,7 +113,7 @@ private void handleUploadAndProblems(UUID jobUUID, HttpServletRequest request, S } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("Encoding not support - should never happen", e); + throw new IllegalStateException("Encoding not encryptionSupport - should never happen", e); } catch (FileUploadException e) { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusService.java index 904a407906..d0682cf4c7 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusService.java @@ -4,11 +4,14 @@ import static com.mercedesbenz.sechub.pds.job.PDSJobAssert.*; import static com.mercedesbenz.sechub.pds.util.PDSAssert.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatus; import com.mercedesbenz.sechub.pds.security.PDSRoleConstants; import com.mercedesbenz.sechub.pds.usecase.PDSStep; import com.mercedesbenz.sechub.pds.usecase.UseCaseUserFetchesJobStatus; @@ -26,9 +29,25 @@ public class PDSGetJobStatusService { public PDSJobStatus getJobStatus(UUID jobUUID) { notNull(jobUUID, "job uuid may not be null!"); - PDSJob job = assertJobFound(jobUUID, repository); + PDSJob pdsJob = assertJobFound(jobUUID, repository); - return new PDSJobStatus(job); + PDSJobStatus status = new PDSJobStatus(); + status.setJobUUID(pdsJob.getUUID()); + status.setOwner(pdsJob.getOwner()); + status.setCreated(convertToString(pdsJob.getCreated())); + status.setStarted(convertToString(pdsJob.getStarted())); + status.setEnded(convertToString(pdsJob.getEnded())); + status.setState(pdsJob.getState()); + status.setEncryptionOutOfSync(pdsJob.isEncryptionOutOfSync()); + + return status; + } + + private String convertToString(LocalDateTime localDateTime) { + if (localDateTime == null) { + return ""; + } + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } } diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJob.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJob.java index 3fccecb51e..4159c3bb32 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJob.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJob.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.pds.job; -import static jakarta.persistence.EnumType.STRING; +import static jakarta.persistence.EnumType.*; import java.sql.Types; import java.time.LocalDateTime; @@ -52,8 +52,6 @@ public class PDSJob { public static final String COLUMN_STARTED = "STARTED"; public static final String COLUMN_ENDED = "ENDED"; - public static final String COLUMN_CONFIGURATION = "CONFIGURATION"; - public static final String COLUMN_RESULT = "RESULT"; public static final String COLUMN_ERROR_STREAM_TEXT = "ERROR_STREAM_TEXT"; @@ -67,6 +65,12 @@ public class PDSJob { public static final String COLUMN_LAST_STREAM_TEXT_REFRESH_REQUEST = "LAST_STREAM_TEXT_REFRESH_REQUEST"; public static final String COLUMN_LAST_STREAM_TEXT_UPDATE = "LAST_STREAM_TEXT_UPDATE"; + public static final String COLUMN_ENCRYPTED_CONFIGURATION = "ENCRYPTED_CONFIGURATION"; + + public static final String COLUMN_ENCRYPT_INITIAL_VECTOR = "ENCRYPT_INITIAL_VECTOR"; + + public static final String COLUMN_ENCRYPTION_OUT_OF_SYNC = "ENCRYPTION_OUT_OF_SYNC"; + /* +-----------------------------------------------------------------------+ */ /* +............................ JPQL .....................................+ */ /* +-----------------------------------------------------------------------+ */ @@ -134,9 +138,6 @@ public class PDSJob { @JsonSerialize(using = SecHubLocalDateTimeSerializer.class) LocalDateTime lastStreamTextUpdate; - @Column(name = COLUMN_CONFIGURATION) - String jsonConfiguration; - @Column(name = COLUMN_RESULT) @JdbcTypeCode(Types.LONGNVARCHAR) // why not using @Lob, because hibernate/postgres issues. see // https://stackoverflow.com/questions/25094410/hibernate-error-while-persisting-text-datatype?noredirect=1#comment39048566_25094410 @@ -166,6 +167,15 @@ public class PDSJob { @JdbcTypeCode(Types.LONGNVARCHAR) // see remarks on COLUMN_RESULT String metaDataText; + @Column(name = COLUMN_ENCRYPTED_CONFIGURATION) + byte[] encryptedConfiguration; + + @Column(name = COLUMN_ENCRYPT_INITIAL_VECTOR) + byte[] encryptionInitialVectorData; + + @Column(name = COLUMN_ENCRYPTION_OUT_OF_SYNC) + boolean encryptionOutOfSync; + public void setServerId(String serverId) { this.serverId = serverId; } @@ -214,10 +224,6 @@ public LocalDateTime getCreated() { return created; } - public String getJsonConfiguration() { - return jsonConfiguration; - } - public PDSJobStatusState getState() { return state; } @@ -250,6 +256,30 @@ public String getMetaDataText() { return metaDataText; } + public byte[] getEncryptedConfiguration() { + return encryptedConfiguration; + } + + public void setEncryptedConfiguration(byte[] encryptedConfiguration) { + this.encryptedConfiguration = encryptedConfiguration; + } + + public byte[] getEncryptionInitialVectorData() { + return encryptionInitialVectorData; + } + + public void setEncryptionInitialVectorData(byte[] encryptionInitialVectorData) { + this.encryptionInitialVectorData = encryptionInitialVectorData; + } + + public void setEncryptionOutOfSync(boolean encryptionOutOfSync) { + this.encryptionOutOfSync = encryptionOutOfSync; + } + + public boolean isEncryptionOutOfSync() { + return encryptionOutOfSync; + } + @Override public int hashCode() { final int prime = 31; diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationAccess.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationAccess.java new file mode 100644 index 0000000000..00215eeb51 --- /dev/null +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationAccess.java @@ -0,0 +1,43 @@ +package com.mercedesbenz.sechub.pds.job; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionException; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionService; + +@Component +public class PDSJobConfigurationAccess { + + @Autowired + PDSEncryptionService encryptionService; + + public PDSJobConfiguration resolveUnEncryptedJobConfiguration(PDSJob job) throws PDSEncryptionException { + if (job == null) { + throw new IllegalArgumentException("job parameter may not be null!"); + } + byte[] encryptedConfiguration = job.getEncryptedConfiguration(); + byte[] encryptionInitialVectorData = job.getEncryptionInitialVectorData(); + + try { + if (encryptedConfiguration == null) { + throw new IllegalStateException("No encrypted configuration found for PDS job: " + job.getUUID()); + } + if (encryptionInitialVectorData == null) { + throw new IllegalStateException("No initial vector data found for PDS job: " + job.getUUID()); + } + + String json = encryptionService.decryptString(encryptedConfiguration, new InitializationVector(encryptionInitialVectorData)); + PDSJobConfiguration configuration = PDSJobConfiguration.fromJSON(json); + return configuration; + + } catch (Exception e) { + throw new PDSEncryptionException(""" + Was not able to decrypt job configuration for PDS job: %s + Suggestion: start a new PDS job with same configuration. + """.formatted(job.getUUID()), e); + } + } + +} diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidator.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidator.java index fac700aea2..138fb0e8f9 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidator.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidator.java @@ -54,7 +54,7 @@ private String createValidationErrorMessage(PDSJobConfiguration configuration) { /* check setup */ PDSProductSetup productSetup = serverConfigurationService.getProductSetupOrNull(productId); if (productSetup == null) { - return "configured PDS instance does not support product identifier:" + productId; + return "configured PDS instance does not encryptionSupport product identifier:" + productId; } List mandatories = productSetup.getParameters().getMandatory(); for (PDSProductParameterDefinition mandatory : mandatories) { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobRestController.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobRestController.java index b7462f25c8..fb797fbb01 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobRestController.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobRestController.java @@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatus; import com.mercedesbenz.sechub.pds.PDSAPIConstants; import com.mercedesbenz.sechub.pds.security.PDSRoleConstants; import com.mercedesbenz.sechub.pds.usecase.PDSStep; diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobStatus.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobStatus.java deleted file mode 100644 index cb338ab003..0000000000 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobStatus.java +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.pds.job; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.UUID; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; -import com.mercedesbenz.sechub.pds.commons.core.PDSJSONConverter; -import com.mercedesbenz.sechub.pds.commons.core.PDSJSONConverterException; - -/** - * This class represents the schedule job status which can be obtained by REST - * - * @author Albert Tregnaghi - * - */ -@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonInclude(Include.NON_NULL) -public class PDSJobStatus { - - public static final String PROPERTY_JOBUUID = "jobUUID"; - public static final String PROPERTY_OWNER = "owner"; - public static final String PROPERTY_CREATED = "created"; - public static final String PROPERTY_STARTED = "started"; - public static final String PROPERTY_ENDED = "ended"; - public static final String PROPERTY_STATE = "state"; - - UUID jobUUID; - - String owner; - - String created; - String started; - String ended; - - String state; - - PDSJobStatus() { - - } - - public PDSJobStatus(PDSJob secHubJob) { - this.jobUUID = secHubJob.getUUID(); - - this.owner = secHubJob.getOwner(); - - this.created = convertToString(secHubJob.getCreated()); - this.started = convertToString(secHubJob.getStarted()); - this.ended = convertToString(secHubJob.getEnded()); - - this.state = convertToString(secHubJob.getState()); - } - - private String convertToString(PDSJobStatusState result) { - if (result == null) { - return ""; - } - return result.name(); - } - - private String convertToString(LocalDateTime localDateTime) { - if (localDateTime == null) { - return ""; - } - return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); - } - - public String toJSON() throws PDSJSONConverterException { - return PDSJSONConverter.get().toJSON(this); - } -} \ No newline at end of file diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobTransactionService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobTransactionService.java index a98cf72970..5ed48fa1f7 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobTransactionService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobTransactionService.java @@ -18,6 +18,7 @@ import com.mercedesbenz.sechub.commons.model.SecHubMessagesList; import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionException; import com.mercedesbenz.sechub.pds.execution.PDSExecutionData; import com.mercedesbenz.sechub.pds.security.PDSRoleConstants; import com.mercedesbenz.sechub.pds.usecase.PDSStep; @@ -36,6 +37,9 @@ public class PDSJobTransactionService { @Autowired PDSJobRepository repository; + @Autowired + PDSJobConfigurationAccess access; + public PDSJobTransactionService() { } @@ -115,16 +119,14 @@ private void updateJobInOwnTransaction(UUID jobUUID, String result, LocalDateTim * * @param jobUUID * @return job configuration, will fail when job is not found + * @throws PDSEncryptionException when it was not possible to access the + * encrypted configuration data */ - public JobConfigurationData getJobConfigurationData(UUID jobUUID) { + public JobConfigurationData getJobConfigurationDataOrFail(UUID jobUUID) throws PDSEncryptionException { PDSJob job = assertJobFound(jobUUID, repository); - JobConfigurationData data = new JobConfigurationData(); - data.jobConfigurationJson = job.getJsonConfiguration(); - data.metaData = job.getMetaDataText(); - - return data; + return new JobConfigurationData(access.resolveUnEncryptedJobConfiguration(job), job.getMetaDataText()); } /** @@ -166,14 +168,14 @@ public void updateJobMessagesInOwnTransaction(UUID jobUUID, SecHubMessagesList s } public void markJobAsCancelRequestedInOwnTransaction(UUID jobUUID) { - updatJobStatusState(jobUUID, PDSJobStatusState.CANCEL_REQUESTED); + updateJobStatusState(jobUUID, PDSJobStatusState.CANCEL_REQUESTED); } public void markJobAsCanceledInOwnTransaction(UUID jobUUID) { - updatJobStatusState(jobUUID, PDSJobStatusState.CANCELED); + updateJobStatusState(jobUUID, PDSJobStatusState.CANCELED); } - private void updatJobStatusState(UUID jobUUID, PDSJobStatusState state) { + private void updateJobStatusState(UUID jobUUID, PDSJobStatusState state) { PDSJob job = assertJobFound(jobUUID, repository); PDSJobStatusState oldState = job.getState(); if (state == oldState) { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspacePreparationContextFactory.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspacePreparationContextFactory.java index ab34430006..a135eb211d 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspacePreparationContextFactory.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspacePreparationContextFactory.java @@ -31,7 +31,7 @@ public class PDSWorkspacePreparationContextFactory { public PDSWorkspacePreparationContext createPreparationContext(PDSJobConfigurationSupport configurationSupport) { if (configurationSupport == null) { - throw new IllegalArgumentException("configuration support may not be null!"); + throw new IllegalArgumentException("configuration encryptionSupport may not be null!"); } PDSWorkspacePreparationContext preparationContext = new PDSWorkspacePreparationContext(); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java index e35b3ca982..e8d1fedd53 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java @@ -146,7 +146,7 @@ public void throwException(String message, Exception cause) throws IOException { * * * @param pdsJobUUID - * @param config + * @param configuration * @param metaData * @return {@link PDSWorkspacePreparationResult}, never null * @throws IOException @@ -177,7 +177,7 @@ private void writeMetaData(UUID jobUUID, String metaData) throws IOException { LOG.debug("Meta data found for PDS job {} - will create metadata file {}", jobUUID, metaDataFile); TextFileWriter writer = new TextFileWriter(); - writer.save(metaDataFile, metaData, true); + writer.writeTextToFile(metaDataFile, metaData, true); LOG.info("Created meta data file for PDS job {}", jobUUID); } } @@ -592,7 +592,7 @@ public void sendEvent(UUID jobUUID, ExecutionEventType eventType, ExecutionEvent LOG.debug("Send event {} to workspace for sechub job: {}", eventType, jobUUID); try { - textFileWriter.save(eventFileToWrite, eventJson, true); + textFileWriter.writeTextToFile(eventFileToWrite, eventJson, true); } catch (IOException e) { LOG.error("Was not able to send event: {} with text: '{}' to workspace for PDS job: {}", eventType, eventJson, jobUUID, e); throw new IllegalStateException("Execution event storage failed for job: " + jobUUID, e); @@ -622,7 +622,7 @@ public ExecutionEventData fetchEventDataOrNull(UUID jobUUID, ExecutionEventType } String json = null; try { - json = textFileReader.loadTextFile(eventFileToRead); + json = textFileReader.readTextFromFile(eventFileToRead); } catch (IOException e) { LOG.error("Was not able to read event: {} from file: '{}' of PDS job: {}", eventType, eventFileToRead, jobUUID, e); throw new IllegalStateException("Execution event reading failed for job: " + jobUUID, e); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/security/PDSSecurityConfiguration.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/security/PDSSecurityConfiguration.java index f3b127cd2e..7e2df4336b 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/security/PDSSecurityConfiguration.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/security/PDSSecurityConfiguration.java @@ -33,7 +33,7 @@ public class PDSSecurityConfiguration { private static final String KEY_ADMIN_USERID = "pds.admin.userid"; private static final String KEY_ADMIN_APITOKEN = "pds.admin.apitoken"; - @PDSMustBeDocumented(value = "Techuser user id", scope = "credentials.") + @PDSMustBeDocumented(value = "Techuser user id.", scope = "credentials.") @Value("${" + KEY_TECHUSER_USERID + "}") String techUserId; diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/SystemTimeProvider.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/PDSSystemTimeProvider.java similarity index 93% rename from sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/SystemTimeProvider.java rename to sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/PDSSystemTimeProvider.java index 2b998979d2..6807014216 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/SystemTimeProvider.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/PDSSystemTimeProvider.java @@ -14,7 +14,7 @@ * */ @Component -public class SystemTimeProvider { +public class PDSSystemTimeProvider { public LocalDateTime getNow() { return LocalDateTime.now(); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/TimeCalculationService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/PDSTimeCalculationService.java similarity index 89% rename from sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/TimeCalculationService.java rename to sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/PDSTimeCalculationService.java index 9925c5da45..9e2a97978f 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/TimeCalculationService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/time/PDSTimeCalculationService.java @@ -7,10 +7,10 @@ import org.springframework.stereotype.Service; @Service -public class TimeCalculationService { +public class PDSTimeCalculationService { @Autowired - SystemTimeProvider systemTime; + PDSSystemTimeProvider systemTime; /** * Calculates current time stamp minus days. When days are negative the days are diff --git a/sechub-pds/src/main/resources/application-pds_integrationtest.yml b/sechub-pds/src/main/resources/application-pds_integrationtest.yml index 022027b4dc..2a4e2e0823 100644 --- a/sechub-pds/src/main/resources/application-pds_integrationtest.yml +++ b/sechub-pds/src/main/resources/application-pds_integrationtest.yml @@ -31,6 +31,14 @@ pds: userid: pds-inttest-admin apitoken: '{noop}pds-inttest-apitoken' + encryption: + algorithm: AES_GCM_SIV_256 + # Test key to have encryption also in integraation tests + # (this is an exception: normally only environment variables are accepted, but + # for integration tests we allow this one) + # In spring boot smoke test this is not set, there is NONE used which is the default + secret-key: nj3AS0UOcA4d/K5cIfOUXipkB/x9oJAVZpVwLqJ5LJE= + logging: level: com.mercedesbenz.sechub: DEBUG diff --git a/sechub-pds/src/main/resources/db/migration/U6__encryption.sql b/sechub-pds/src/main/resources/db/migration/U6__encryption.sql new file mode 100644 index 0000000000..90213bc28c --- /dev/null +++ b/sechub-pds/src/main/resources/db/migration/U6__encryption.sql @@ -0,0 +1,6 @@ +-- SPDX-License-Identifier: MIT +ALTER TABLE pds_job DROP COLUMN encrypted_configuration bytea; +ALTER TABLE pds_job DROP COLUMN encrypt_initial_vector bytea; +ALTER TABLE pds_job DROP COLUMN encryption_out_of_sync; + +ALTER TABLE pds_job ADD COLUMN configuration varchar(8192) not null; diff --git a/sechub-pds/src/main/resources/db/migration/V6__encryption.sql b/sechub-pds/src/main/resources/db/migration/V6__encryption.sql new file mode 100644 index 0000000000..da047c8489 --- /dev/null +++ b/sechub-pds/src/main/resources/db/migration/V6__encryption.sql @@ -0,0 +1,13 @@ +-- SPDX-License-Identifier: MIT + +-- New encryption columns. +-- We allow null values here explicit. This happens only for old unencrypted data +-- and will result in restarts of current running unencrypted jobs (if necessary at all) +-- by SecHub +ALTER TABLE pds_job ADD COLUMN encrypted_configuration bytea; +ALTER TABLE pds_job ADD COLUMN encrypt_initial_vector bytea; +ALTER TABLE pds_job ADD COLUMN encryption_out_of_sync boolean; + +-- Delete old configuration column (+data), existing data will be lost - this is a wanted behavor +-- see comment before. +ALTER TABLE pds_job DROP COLUMN configuration; diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsedTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsedTest.java index fb37b78dce..2294016507 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsedTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/PDSStartupAssertEnvironmentVariablesUsedTest.java @@ -17,6 +17,7 @@ import com.mercedesbenz.sechub.commons.core.environment.SecureEnvironmentVariableKeyValueRegistry; import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironmentVariableSupport; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionConfiguration; import com.mercedesbenz.sechub.pds.security.PDSSecurityConfiguration; import com.mercedesbenz.sechub.pds.storage.PDSS3PropertiesSetup; import com.mercedesbenz.sechub.pds.storage.PDSSharedVolumePropertiesSetup; @@ -29,6 +30,7 @@ class PDSStartupAssertEnvironmentVariablesUsedTest { private PDSSecurityConfiguration securityConfiguration; private PDSSharedVolumePropertiesSetup sharedVolumeSetup; private Environment environment; + private PDSEncryptionConfiguration encryptionConfiguration; @BeforeEach void beforeEach() { @@ -38,6 +40,8 @@ void beforeEach() { securityConfiguration = mock(PDSSecurityConfiguration.class); envVariableSupport = mock(SystemEnvironmentVariableSupport.class); sharedVolumeSetup = mock(PDSSharedVolumePropertiesSetup.class); + encryptionConfiguration = mock(PDSEncryptionConfiguration.class); + environment = mock(Environment.class); assertionToTest.envVariableSupport = envVariableSupport; @@ -45,6 +49,7 @@ void beforeEach() { assertionToTest.s3Setup = s3setup; assertionToTest.sharedVolumeSetup = sharedVolumeSetup; assertionToTest.environment = environment; + assertionToTest.encryptionConfiguration = encryptionConfiguration; } diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupServiceTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupServiceTest.java index ab25ee6ea4..4f267d038f 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupServiceTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/autocleanup/PDSAutoCleanupServiceTest.java @@ -15,14 +15,14 @@ import com.mercedesbenz.sechub.pds.config.PDSConfigService; import com.mercedesbenz.sechub.pds.job.PDSJobRepository; -import com.mercedesbenz.sechub.pds.time.TimeCalculationService; +import com.mercedesbenz.sechub.pds.time.PDSTimeCalculationService; class PDSAutoCleanupServiceTest { private PDSAutoCleanupService serviceToTest; private PDSConfigService configService; private PDSJobRepository jobRepository; - private TimeCalculationService timeCalculationService; + private PDSTimeCalculationService pdsTimeCalculationService; private PDSAutoCleanupResultInspector inspector; @BeforeEach @@ -31,12 +31,12 @@ void beforeEach() { configService = mock(PDSConfigService.class); jobRepository = mock(PDSJobRepository.class); - timeCalculationService = mock(TimeCalculationService.class); + pdsTimeCalculationService = mock(PDSTimeCalculationService.class); inspector = mock(PDSAutoCleanupResultInspector.class); serviceToTest.configService = configService; serviceToTest.jobRepository = jobRepository; - serviceToTest.timeCalculationService = timeCalculationService; + serviceToTest.PDSTimeCalculationService = pdsTimeCalculationService; serviceToTest.inspector = inspector; } @@ -46,14 +46,14 @@ void cleanup_executes_NOT_delete_job_when_config_service_returns_minus_1_day() { long days = -1; when(configService.getAutoCleanupInDays()).thenReturn(days); LocalDateTime cleanTime = LocalDateTime.now().minusDays(days); - when(timeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); + when(pdsTimeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); /* execute */ serviceToTest.cleanup(); /* test */ verify(configService).getAutoCleanupInDays(); - verify(timeCalculationService, never()).calculateNowMinusDays(any()); + verify(pdsTimeCalculationService, never()).calculateNowMinusDays(any()); verify(jobRepository, never()).deleteJobOlderThan(any()); // check inspection as expected: never because not executed verify(inspector, never()).inspect(any()); @@ -65,14 +65,14 @@ void cleanup_executes_NOT_delete_job_when_config_servic_returns_0_days() { long days = 0; when(configService.getAutoCleanupInDays()).thenReturn(days); LocalDateTime cleanTime = LocalDateTime.now().minusDays(days); - when(timeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); + when(pdsTimeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); /* execute */ serviceToTest.cleanup(); /* test */ verify(configService).getAutoCleanupInDays(); - verify(timeCalculationService, never()).calculateNowMinusDays(any()); + verify(pdsTimeCalculationService, never()).calculateNowMinusDays(any()); verify(jobRepository, never()).deleteJobOlderThan(any()); // check inspection as expected: never because not executed verify(inspector, never()).inspect(any()); @@ -84,7 +84,7 @@ void cleanup_executes_delete_job_when_config_service_returns_days_bigger_than_ze /* prepare */ when(configService.getAutoCleanupInDays()).thenReturn(days); LocalDateTime cleanTime = LocalDateTime.now().minusDays(days); - when(timeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); + when(pdsTimeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); when(jobRepository.deleteJobOlderThan(cleanTime)).thenReturn(1234); /* execute */ @@ -92,7 +92,7 @@ void cleanup_executes_delete_job_when_config_service_returns_days_bigger_than_ze /* test */ verify(configService).getAutoCleanupInDays(); - verify(timeCalculationService).calculateNowMinusDays(eq(days)); + verify(pdsTimeCalculationService).calculateNowMinusDays(eq(days)); verify(jobRepository).deleteJobOlderThan(cleanTime); // check inspection as expected diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionConfigurationTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionConfigurationTest.java new file mode 100644 index 0000000000..ba634b68ba --- /dev/null +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionConfigurationTest.java @@ -0,0 +1,114 @@ +package com.mercedesbenz.sechub.pds.encryption; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +class PDSEncryptionConfigurationTest { + + private PDSEncryptionConfiguration configurationToTest; + + @BeforeEach + void beforeEach() { + configurationToTest = new PDSEncryptionConfiguration(); + } + + @ParameterizedTest + @EnumSource(value = PDSCipherAlgorithm.class, mode = Mode.EXCLUDE, names = "NONE") + void init_with_empty_key_throws_exception(PDSCipherAlgorithm algorithm) throws Exception { + + /* prepare */ + configurationToTest.algorithmAsString = algorithm.name(); + configurationToTest.secretKeyAsString = ""; + + /* execute + test @formatter:off */ + assertThatThrownBy(() -> + configurationToTest.init()). + isInstanceOf(PDSEncryptionException.class). + hasMessageContaining(algorithm+" does not allow an empty secret key"); + + /* @formatter:on */ + + } + + @ParameterizedTest + @EmptySource + @NullSource + void init_with_invalid_key_throws_NO_exception_for_NONE_algorithm(String invalidSecretKey) throws Exception { + + /* prepare */ + configurationToTest.algorithmAsString = PDSCipherAlgorithm.NONE.name(); + configurationToTest.secretKeyAsString = invalidSecretKey; + + /* execute + test @formatter:off */ + assertThatNoException().isThrownBy(() -> + configurationToTest.init()); + /* @formatter:on */ + + } + + @ParameterizedTest + @EnumSource(PDSCipherAlgorithm.class) + void init_resets_secret_key_string_and_setup_internal_data(PDSCipherAlgorithm algorithm) throws Exception { + + /* prepare */ + configurationToTest.algorithmAsString = algorithm.name(); + configurationToTest.secretKeyAsString = "test-secret"; + + /* execute */ + configurationToTest.init(); + + /* test */ + assertThat(configurationToTest.secretKeyAsString).isNull(); // reset is done + assertThat(configurationToTest.algorithmAsString).isNotNull(); + + assertThat(configurationToTest.getAlgorithm()).isEqualTo(algorithm); + assertThat(configurationToTest.getSecretKeyBytes()).isEqualTo("test-secret".getBytes()); + + } + + @ParameterizedTest + @EnumSource(PDSCipherAlgorithm.class) + void init_resets_secret_key_string_and_setup_internal_data_lowercase_works_as_well(PDSCipherAlgorithm algorithm) throws Exception { + + /* prepare */ + configurationToTest.algorithmAsString = algorithm.name().toLowerCase(); + configurationToTest.secretKeyAsString = "test-secret"; + + /* execute */ + configurationToTest.init(); + + /* test */ + assertThat(configurationToTest.secretKeyAsString).isNull(); // reset is done + assertThat(configurationToTest.algorithmAsString).isNotNull(); + + assertThat(configurationToTest.getAlgorithm()).isEqualTo(algorithm); + assertThat(configurationToTest.getSecretKeyBytes()).isEqualTo("test-secret".getBytes()); + + } + + @ParameterizedTest + @ValueSource(strings = "unknown") + @EmptySource + @NullSource + void init_unknown_algorithm_throws_exception(String wrongAlgorithmAsText) throws Exception { + + /* prepare */ + configurationToTest.algorithmAsString = wrongAlgorithmAsText; + configurationToTest.secretKeyAsString = "test-secret"; + + /* execute + test */ + assertThatThrownBy(() -> configurationToTest.init()).isInstanceOf(PDSEncryptionException.class).hasMessageContaining("not supported"); + + /* test */ + assertThat(configurationToTest.secretKeyAsString).isNull(); // reset is still done + + } + +} diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionServiceTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionServiceTest.java new file mode 100644 index 0000000000..a6bcc1fbe1 --- /dev/null +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/encryption/PDSEncryptionServiceTest.java @@ -0,0 +1,102 @@ +package com.mercedesbenz.sechub.pds.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.Base64; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.ArgumentCaptor; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; + +class PDSEncryptionServiceTest { + + private PDSEncryptionConfiguration configuration; + private PDSEncryptionService serviceToTest; + private PersistentCipherFactory cipherFactory; + private EncryptionSupport encryptionSupport; + + @BeforeEach + public void beforeEach() throws Exception { + configuration = mock(PDSEncryptionConfiguration.class); + cipherFactory = mock(PersistentCipherFactory.class); + encryptionSupport = mock(EncryptionSupport.class); + + serviceToTest = new PDSEncryptionService(); + + serviceToTest.configuration = configuration; + serviceToTest.cipherFactory = cipherFactory; + serviceToTest.encryptionSupport = encryptionSupport; + } + + @ParameterizedTest + @EnumSource(PDSCipherAlgorithm.class) + void init_creates_expected_cipher_with_secret_key_inside(PDSCipherAlgorithm algorithm) throws Exception { + + /* prepare */ + String testSecretPlainText = "test-secret"; + + when(configuration.getAlgorithm()).thenReturn(algorithm); + String base64String = Base64.getEncoder().encodeToString(testSecretPlainText.getBytes()); + when(configuration.getSecretKeyBytes()).thenReturn(base64String.getBytes()); + + PersistentCipher cipher = mock(PersistentCipher.class); + when(cipherFactory.createCipher(any(), eq(algorithm.getType()))).thenReturn(cipher); + + /* execute */ + serviceToTest.init(); + + /* test */ + assertThat(serviceToTest.cipher).isEqualTo(cipher); + ArgumentCaptor captor = ArgumentCaptor.forClass(SecretKeyProvider.class); + verify(cipherFactory).createCipher(captor.capture(), eq(algorithm.getType())); + + SecretKeyProvider capturedProvider = captor.getValue(); + switch (algorithm) { + case NONE: + assertThat(capturedProvider).isNull(); // no secret key provider for NONE + break; + default: + assertThat(capturedProvider.getSecretKey().getEncoded()).isEqualTo(testSecretPlainText.getBytes()); + break; + + } + + } + + @Test + void decryptString_returns_decrypted_string_by_encryption_support() throws Exception { + + /* prepare */ + + InitializationVector initialVector = mock(InitializationVector.class); + byte[] encryptedData = "encrypted-data".getBytes(); + + PersistentCipher cipher = mock(PersistentCipher.class); + when(cipherFactory.createCipher(any(), any())).thenReturn(cipher); + PDSCipherAlgorithm algorithm = mock(PDSCipherAlgorithm.class); + when(configuration.getAlgorithm()).thenReturn(algorithm); + + serviceToTest.init(); + + String resultFromEncryptionSupport = "encrypted test data from encryption support..."; + when(encryptionSupport.decryptString(encryptedData, cipher, initialVector)).thenReturn(resultFromEncryptionSupport); + + /* execute */ + String result = serviceToTest.decryptString(encryptedData, initialVector); + + /* test */ + assertThat(result).isEqualTo(resultFromEncryptionSupport); + + } + +} diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSApplyFutureExecutionResultToJobServiceTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSApplyFutureExecutionResultToJobServiceTest.java new file mode 100644 index 0000000000..77dd4149f6 --- /dev/null +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSApplyFutureExecutionResultToJobServiceTest.java @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.pds.execution; + +import static org.mockito.Mockito.*; + +import java.util.concurrent.Future; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; +import com.mercedesbenz.sechub.pds.job.PDSJob; +import com.mercedesbenz.sechub.pds.job.PDSJobRepository; + +class PDSApplyFutureExecutionResultToJobServiceTest { + + private PDSApplyFutureExecutionResultToJobService serviceToTest; + private Future future; + private PDSJob job; + private PDSJobRepository repository; + private PDSExecutionResult result; + + @SuppressWarnings("unchecked") + @BeforeEach + public void beforeEach() throws Exception { + serviceToTest = new PDSApplyFutureExecutionResultToJobService(); + repository = mock(PDSJobRepository.class); + serviceToTest.repository = repository; + + future = mock(Future.class); + job = mock(PDSJob.class); + + result = mock(PDSExecutionResult.class); + when(future.get()).thenReturn(result); + } + + @Test + void future_canceled_then_job_is_set_to_canceled_and_then_stored() throws Exception { + + /* prepare */ + when(future.isCancelled()).thenReturn(true); + + /* execute */ + serviceToTest.applyResultToJob(future, job); + + /* test */ + InOrder inOrder = inOrder(job, repository); + inOrder.verify(job).setState(PDSJobStatusState.CANCELED); + inOrder.verify(repository).save(job); + + } + + @Test + void job_with_result() throws Exception { + + /* prepare */ + when(future.isCancelled()).thenReturn(false); + when(result.getResult()).thenReturn("result1"); + when(result.isEncryptionFailure()).thenReturn(false); + when(result.isFailed()).thenReturn(false); + when(result.isCanceled()).thenReturn(false); + + /* execute */ + serviceToTest.applyResultToJob(future, job); + + /* test */ + InOrder inOrder = inOrder(job, repository); + inOrder.verify(job).setResult("result1"); + inOrder.verify(job).setState(PDSJobStatusState.DONE); + inOrder.verify(repository).save(job); + + } + + @Test + void job_canceled() throws Exception { + + /* prepare */ + when(future.isCancelled()).thenReturn(false); + when(result.getResult()).thenReturn(""); + when(result.isEncryptionFailure()).thenReturn(false); + when(result.isFailed()).thenReturn(false); + when(result.isCanceled()).thenReturn(true); + + /* execute */ + serviceToTest.applyResultToJob(future, job); + + /* test */ + InOrder inOrder = inOrder(job, repository); + inOrder.verify(job).setResult(""); + inOrder.verify(job).setState(PDSJobStatusState.CANCELED); + inOrder.verify(repository).save(job); + + } + + @Test + void job_with_encryption_failure() throws Exception { + + /* prepare */ + when(future.isCancelled()).thenReturn(false); + when(result.getResult()).thenReturn(""); + when(result.isFailed()).thenReturn(true); + when(result.isCanceled()).thenReturn(false); + when(result.isEncryptionFailure()).thenReturn(true); + + /* execute */ + serviceToTest.applyResultToJob(future, job); + + /* test */ + InOrder inOrder = inOrder(job, repository); + inOrder.verify(job).setResult(""); + inOrder.verify(job).setState(PDSJobStatusState.FAILED); + inOrder.verify(job).setEncryptionOutOfSync(true); + inOrder.verify(repository).save(job); + + } + +} diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallableTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallableTest.java index c60c14ffb3..d3c4ad89f7 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallableTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallableTest.java @@ -6,7 +6,6 @@ import static org.mockito.Mockito.*; import java.io.File; -import java.io.IOException; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -21,9 +20,11 @@ import com.mercedesbenz.sechub.commons.model.SecHubMessagesList; import com.mercedesbenz.sechub.commons.pds.PDSProcessAdapterFactory; import com.mercedesbenz.sechub.commons.pds.ProcessAdapter; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionException; import com.mercedesbenz.sechub.pds.job.JobConfigurationData; import com.mercedesbenz.sechub.pds.job.PDSCheckJobStatusService; import com.mercedesbenz.sechub.pds.job.PDSGetJobStreamService; +import com.mercedesbenz.sechub.pds.job.PDSJobConfiguration; import com.mercedesbenz.sechub.pds.job.PDSJobTransactionService; import com.mercedesbenz.sechub.pds.job.PDSWorkspacePreparationResult; import com.mercedesbenz.sechub.pds.job.PDSWorkspaceService; @@ -53,7 +54,7 @@ class PDSExecutionCallableTest { private ProductLaunchProcessHandlingData launchProcessHandlingData; @BeforeEach - void beforeEach() throws IOException { + void beforeEach() throws Exception { jobUUID = UUID.randomUUID(); workspaceTestDataJob1Folder = new File("./src/test/resources/testdata/workspace1/job1"); resultFile = new File(workspaceTestDataJob1Folder, "result.txt"); @@ -98,10 +99,11 @@ void beforeEach() throws IOException { PDSWorkspacePreparationResult launchScriptExecutableResult = new PDSWorkspacePreparationResult(true); when(workspaceService.prepare(eq(jobUUID), any(), any())).thenReturn(launchScriptExecutableResult); - when(data.getJobConfigurationJson()).thenReturn("{}"); + PDSJobConfiguration configuration = new PDSJobConfiguration(); + when(data.getJobConfiguration()).thenReturn(configuration); when(processAdapterFactory.startProcess(any())).thenReturn(processAdapter); - when(jobTransactionService.getJobConfigurationData(jobUUID)).thenReturn(data); + when(jobTransactionService.getJobConfigurationDataOrFail(jobUUID)).thenReturn(data); when(workspaceService.getProductPathFor(any())).thenReturn("the/path/to/product.sh"); when(workspaceService.createLocationData(jobUUID)).thenReturn(locationData); when(workspaceService.getMessagesFolder(jobUUID)).thenReturn(messageFolder); @@ -134,12 +136,13 @@ void timeout_with_0_fails_without_job_transaction_write() throws Exception { /* test */ assertJobHasMarkedAsRunningInOwnTransaction(); - assertTrue(result.failed); + assertTrue(result.isFailed()); // internally an illegal state is thrown before any execution, verify(processAdapterFactory, never()).startProcess(any()); verify(jobTransactionService, never()).updateJobExecutionDataInOwnTransaction(any(), any()); // check there is no wait for a process at all verify(processAdapter, never()).waitFor(anyLong(), any()); + assertFalse(result.isEncryptionFailure()); } @Test @@ -152,8 +155,8 @@ void a_timeout_does_write_execution_result_data() throws Exception { /* test */ assertJobHasMarkedAsRunningInOwnTransaction(); - assertTrue(result.failed); - assertEquals("Product time out.", result.result); + assertTrue(result.isFailed()); + assertEquals("Product time out.", result.getResult()); ArgumentCaptor executionDataCaptor = ArgumentCaptor.forClass(PDSExecutionData.class); verify(jobTransactionService).updateJobExecutionDataInOwnTransaction(eq(jobUUID), executionDataCaptor.capture()); @@ -162,6 +165,7 @@ void a_timeout_does_write_execution_result_data() throws Exception { assertEquals("the output", executionData.getOutputStreamData()); assertEquals("an error", executionData.getErrorStreamData()); assertEquals("meta data", executionData.getMetaData()); + assertFalse(result.isEncryptionFailure()); } @@ -175,8 +179,8 @@ void no_timeout_does_not_fail_and_writes_execution_result_data() throws Exceptio /* test */ assertJobHasMarkedAsRunningInOwnTransaction(); - assertFalse(result.failed); - assertEquals("the result", result.result); + assertFalse(result.isFailed()); + assertEquals("the result", result.getResult()); ArgumentCaptor executionDataCaptor = ArgumentCaptor.forClass(PDSExecutionData.class); verify(jobTransactionService).updateJobExecutionDataInOwnTransaction(eq(jobUUID), executionDataCaptor.capture()); @@ -185,6 +189,7 @@ void no_timeout_does_not_fail_and_writes_execution_result_data() throws Exceptio assertEquals("the output", executionData.getOutputStreamData()); assertEquals("an error", executionData.getErrorStreamData()); assertEquals("meta data", executionData.getMetaData()); + assertFalse(result.isEncryptionFailure()); } @Test @@ -197,9 +202,9 @@ void no_timeout_does_not_fail_and_writes_messages() throws Exception { /* test */ assertJobHasMarkedAsRunningInOwnTransaction(); - assertFalse(result.failed); + assertFalse(result.isFailed()); assertJob1MessageHasBeenPersistedToDB(); - + assertFalse(result.isEncryptionFailure()); } @Test @@ -212,8 +217,9 @@ void a_timeout_does_fail_and_writes_messages() throws Exception { /* test */ assertJobHasMarkedAsRunningInOwnTransaction(); - assertTrue(result.failed); + assertTrue(result.isFailed()); assertJob1MessageHasBeenPersistedToDB(); + assertFalse(result.isEncryptionFailure()); } @@ -230,6 +236,19 @@ void process_waiting_is_done_with_value_from_handling_data_factory_and_time_unit verify(processAdapter).waitFor(value, TimeUnit.MINUTES); } + @Test + void when_getJobConfigurationDataOrFail_fails_the_exception_leads_to_a_job_marked_as_encryption_out_of_synch() throws Exception { + /* prepare */ + when(jobTransactionService.getJobConfigurationDataOrFail(any())).thenThrow(new PDSEncryptionException("some text")); + simulateProcessDone(); + + /* execute */ + PDSExecutionResult result = callableToTest.call(); + + /* test */ + assertTrue(result.isEncryptionFailure()); + } + /* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */ /* + ................Helpers......................... + */ /* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */ diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSMessageCollectorTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSMessageCollectorTest.java index 6d5c72d68c..f5acce4e5d 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSMessageCollectorTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/execution/PDSMessageCollectorTest.java @@ -63,7 +63,7 @@ void when_folder_has_one_file_with_not_ERROR_or_WARNING_at_the_beginning_an_info String messageText = "I am a message!"; TestFileWriter writer = new TestFileWriter(); - writer.save(new File(tempDir, fileName), messageText, false); + writer.writeTextToFile(new File(tempDir, fileName), messageText, false); /* execute */ List result = collectorToTest.collect(tempDir); @@ -86,7 +86,7 @@ void when_folder_has_one_file_with_WARNING_at_the_beginning_a_warning_message_is String messageText = "I am a warn message!"; TestFileWriter writer = new TestFileWriter(); - writer.save(new File(tempDir, fileName), messageText, false); + writer.writeTextToFile(new File(tempDir, fileName), messageText, false); /* execute */ List result = collectorToTest.collect(tempDir); @@ -109,7 +109,7 @@ void when_folder_has_one_file_with_ERROR_at_the_beginning_a_error_message_is_res String messageText = "I am an error message!"; TestFileWriter writer = new TestFileWriter(); - writer.save(new File(tempDir, fileName), messageText, false); + writer.writeTextToFile(new File(tempDir, fileName), messageText, false); /* execute */ List result = collectorToTest.collect(tempDir); diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobServiceTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobServiceTest.java index 2b480b73c2..3c94420863 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobServiceTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSCreateJobServiceTest.java @@ -1,28 +1,25 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.pds.job; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.UUID; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; import com.mercedesbenz.sechub.pds.PDSNotAcceptableException; import com.mercedesbenz.sechub.pds.config.PDSServerConfigurationService; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionService; import com.mercedesbenz.sechub.pds.security.PDSUserContextService; -import com.mercedesbenz.sechub.test.junit4.ExpectedExceptionFactory; public class PDSCreateJobServiceTest { - @Rule - public ExpectedException expected = ExpectedExceptionFactory.none(); - private PDSCreateJobService serviceToTest; private UUID sechubJobUUID; private PDSJobRepository repository; @@ -32,8 +29,11 @@ public class PDSCreateJobServiceTest { private PDSJobConfigurationValidator configurationValidator; private PDSServerConfigurationService serverConfigurationService; - @Before - public void before() throws Exception { + private PDSEncryptionService encryptionService; + private EncryptionResult encryptionResult; + + @BeforeEach + void before() throws Exception { sechubJobUUID = UUID.randomUUID(); createdJob1UUID = UUID.randomUUID(); repository = mock(PDSJobRepository.class); @@ -42,21 +42,31 @@ public void before() throws Exception { userContextService = mock(PDSUserContextService.class); when(userContextService.getUserId()).thenReturn("callerName"); + encryptionService = mock(PDSEncryptionService.class); serviceToTest = new PDSCreateJobService(); serviceToTest.repository = repository; serviceToTest.userContextService = userContextService; serviceToTest.configurationValidator = configurationValidator; serviceToTest.serverConfigurationService = serverConfigurationService; + serviceToTest.encryptionService = encryptionService; resultJob1 = new PDSJob(); resultJob1.uUID = createdJob1UUID; when(repository.save(any())).thenReturn(resultJob1); + + // encryption stup + encryptionResult = mock(EncryptionResult.class); + byte[] encryptedBytes = "some-pseudo-encrypted-data".getBytes(); + when(encryptionResult.getEncryptedData()).thenReturn(encryptedBytes); + InitializationVector initialVector = mock(InitializationVector.class); + when(encryptionResult.getInitialVector()).thenReturn(initialVector); + when(encryptionService.encryptString(any())).thenReturn(encryptionResult); } @Test - public void creating_a_job_returns_jobUUD_of_stored_job_in_repository() { + void creating_a_job_returns_jobUUD_of_stored_job_in_repository() { /* prepare */ PDSJobConfiguration configuration = new PDSJobConfiguration(); configuration.setSechubJobUUID(sechubJobUUID); @@ -65,13 +75,13 @@ public void creating_a_job_returns_jobUUD_of_stored_job_in_repository() { PDSJobCreateResult result = serviceToTest.createJob(configuration); /* test */ - assertNotNull(result); + assertThat(result).isNotNull(); UUID pdsJobUUID = result.getJobUUID(); - assertEquals(createdJob1UUID, pdsJobUUID); + assertThat(createdJob1UUID).isEqualTo(pdsJobUUID); } @Test - public void creating_a_job_sets_current_user_as_owner() { + void creating_a_job_sets_current_user_as_owner() { /* prepare */ PDSJobConfiguration configuration = new PDSJobConfiguration(); @@ -79,33 +89,40 @@ public void creating_a_job_sets_current_user_as_owner() { PDSJobCreateResult result = serviceToTest.createJob(configuration); /* test */ - assertNotNull(result); + assertThat(result).isNotNull(); ArgumentCaptor jobCaptor = ArgumentCaptor.forClass(PDSJob.class); verify(repository).save(jobCaptor.capture()); - assertEquals("callerName", jobCaptor.getValue().getOwner()); + assertThat(jobCaptor.getValue().getOwner()).isEqualTo("callerName"); } @Test - public void creating_a_job_sets_configuration_as_json() throws Exception { + void creating_a_job_sets_encrypted_configuration() throws Exception { /* prepare */ PDSJobConfiguration configuration = new PDSJobConfiguration(); + // check precondition + String json = configuration.toJSON(); + // Next line normally not valid, but validator does not throw an exception here, + // so we can have an empty configuration here... Just to test it + assertThat(json).isEqualTo("{\"parameters\":[]}"); + + // simulate encryption for this parameter + byte[] encryptedBytes = ("encrypted:" + json).getBytes(); + when(encryptionResult.getEncryptedData()).thenReturn(encryptedBytes); /* execute */ serviceToTest.createJob(configuration); /* test */ + ArgumentCaptor jobCaptor = ArgumentCaptor.forClass(PDSJob.class); verify(repository).save(jobCaptor.capture()); - String json = configuration.toJSON(); - // Next line normally not valid, but validator does not throw an exception here, - // so we can have an empty config here... Just to test it - assertEquals("{\"parameters\":[]}", json); - assertEquals(json, jobCaptor.getValue().getJsonConfiguration()); + PDSJob persistedJob = jobCaptor.getValue(); + assertThat(persistedJob.getEncryptedConfiguration()).isEqualTo(encryptedBytes); } @Test - public void creating_a_job_calls_configurationValidator() { + void creating_a_job_calls_configurationValidator() { /* prepare */ PDSJobConfiguration configuration = new PDSJobConfiguration(); @@ -117,17 +134,13 @@ public void creating_a_job_calls_configurationValidator() { } @Test - public void creating_a_job_fires_exception_thrown_by_validator() { + void creating_a_job_fires_exception_thrown_by_validator() { /* prepare */ PDSJobConfiguration configuration = new PDSJobConfiguration(); doThrow(new PDSNotAcceptableException("ups")).when(configurationValidator).assertPDSConfigurationValid(configuration); - /* test */ - expected.expect(PDSNotAcceptableException.class); - expected.expectMessage("ups"); - /* execute */ - serviceToTest.createJob(configuration); + assertThatThrownBy(() -> serviceToTest.createJob(configuration)).isInstanceOf(PDSNotAcceptableException.class).hasMessage("ups"); } diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusServiceTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusServiceTest.java index 04c9bebd21..5cd966ede0 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusServiceTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSGetJobStatusServiceTest.java @@ -1,88 +1,97 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.pds.job; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import java.time.LocalDateTime; import java.util.Optional; import java.util.UUID; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatus; import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; import com.mercedesbenz.sechub.pds.PDSNotFoundException; -import com.mercedesbenz.sechub.test.junit4.ExpectedExceptionFactory; public class PDSGetJobStatusServiceTest { - @Rule - public ExpectedException expected = ExpectedExceptionFactory.none(); - + private static final LocalDateTime CREATION_TIME = LocalDateTime.of(2020, 06, 23, 16, 35, 01); + private static final LocalDateTime END_TIME = LocalDateTime.of(2020, 06, 23, 16, 37, 03); private PDSGetJobStatusService serviceToTest; private UUID jobUUID; private PDSJobRepository repository; private PDSJob job; - @Before - public void before() throws Exception { + @BeforeEach + void beforeEach() throws Exception { repository = mock(PDSJobRepository.class); jobUUID = UUID.randomUUID(); - job = new PDSJob(); - job.uUID = jobUUID; - job.created = LocalDateTime.of(2020, 06, 23, 16, 35, 01); - job.owner = "theOwner"; - - when(repository.findById(jobUUID)).thenReturn(Optional.of(job)); serviceToTest = new PDSGetJobStatusService(); serviceToTest.repository = repository; } - @Test - public void get_status_works_for_any_state() { - for (PDSJobStatusState state : PDSJobStatusState.values()) { - String ended = null; - if (PDSJobStatusState.DONE.equals(state)) { - ended = "2020-06-23T16:37:03"; - job.ended = LocalDateTime.of(2020, 06, 23, 16, 37, 03); - } else { - ended = ""; - } - fetchStateWorksFor(state, ended); - } - } - - @Test - public void job_not_found_throws_pds_not_found_exception() { - /* test */ - expected.expect(PDSNotFoundException.class); - expected.expectMessage("Given job does not exist"); + @ParameterizedTest + @EnumSource(PDSJobStatusState.class) + void get_status_works_for_any_state(PDSJobStatusState state) { + /* prepare */ + prepareJob(state, END_TIME, false); /* execute */ - UUID notExistingJobUUID = UUID.randomUUID(); - serviceToTest.getJobStatus(notExistingJobUUID); + PDSJobStatus result = serviceToTest.getJobStatus(jobUUID); + /* test */ + assertThat(result.getOwner()).isEqualTo(job.owner); + assertThat(result.getCreated()).isEqualTo(CREATION_TIME.toString()); + assertThat(result.getEnded()).isEqualTo(END_TIME.toString()); + assertThat(result.getState()).isEqualTo(job.state); + assertThat(result.isEncryptionOutOfSync()).isEqualTo(false); } - private void fetchStateWorksFor(PDSJobStatusState state, String eexpectedEnded) { - /* prepare */ - job.setState(state); + @ParameterizedTest + @EnumSource(PDSJobStatusState.class) + void get_status_works_for_any_state_encryption_out_of_sync(PDSJobStatusState state) { /* prepare */ - job.state = PDSJobStatusState.DONE; + prepareJob(state, END_TIME, true); /* execute */ PDSJobStatus result = serviceToTest.getJobStatus(jobUUID); /* test */ - assertEquals(job.owner, result.owner); - assertEquals("2020-06-23T16:35:01", result.created); - assertEquals(eexpectedEnded, result.ended); - assertEquals(job.state.name(), result.state); + assertThat(result.getOwner()).isEqualTo(job.owner); + assertThat(result.getCreated()).isEqualTo(CREATION_TIME.toString()); + assertThat(result.getEnded()).isEqualTo(END_TIME.toString()); + assertThat(result.getState()).isEqualTo(job.state); + assertThat(result.isEncryptionOutOfSync()).isEqualTo(true); + } + + @Test + void job_not_found_throws_pds_not_found_exception() { + /* prepare */ + UUID notExistingJobUUID = UUID.randomUUID(); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.getJobStatus(notExistingJobUUID)).isInstanceOf(PDSNotFoundException.class) + .hasMessageContaining("Given job does not exist"); + + } + + private PDSJob prepareJob(PDSJobStatusState state, LocalDateTime expectedEnded, boolean encryptionOutOfSync) { + job = new PDSJob(); + job.uUID = jobUUID; + job.created = CREATION_TIME; + job.setOwner("theOwner"); + job.setEnded(expectedEnded); + job.setState(state); + job.setEncryptionOutOfSync(encryptionOutOfSync); + + when(repository.findById(jobUUID)).thenReturn(Optional.of(job)); + return job; } } diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationAccessTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationAccessTest.java new file mode 100644 index 0000000000..8951fc1fca --- /dev/null +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationAccessTest.java @@ -0,0 +1,97 @@ +package com.mercedesbenz.sechub.pds.job; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionException; +import com.mercedesbenz.sechub.pds.encryption.PDSEncryptionService; +import com.mercedesbenz.sechub.test.TestCanaryException; + +class PDSJobConfigurationAccessTest { + + private PDSJobConfigurationAccess accessToTest; + private PDSEncryptionService encryptionService; + + @BeforeEach + public void beforeEach() throws Exception { + accessToTest = new PDSJobConfigurationAccess(); + encryptionService = mock(PDSEncryptionService.class); + + accessToTest.encryptionService = encryptionService; + } + + @Test + void access_can_resolve_pds_job_configuration_from_encrypted_bytes() throws Exception { + + /* prepare */ + byte[] persistedInitialVector = "initme".getBytes(); + byte[] persistedEncryptedConfiguration = "i-am-encrypted".getBytes(); + InitializationVector givenVector = new InitializationVector(persistedInitialVector); + + PDSJob job = mock(PDSJob.class); + when(job.getEncryptionInitialVectorData()).thenReturn(persistedInitialVector); + when(job.getEncryptedConfiguration()).thenReturn(persistedEncryptedConfiguration); + + PDSJobConfiguration configuration = new PDSJobConfiguration(); + String jsonAsPlainText = configuration.toJSON(); + + when(encryptionService.decryptString(eq(persistedEncryptedConfiguration), eq(givenVector))).thenReturn(jsonAsPlainText); + + /* execute */ + PDSJobConfiguration result = accessToTest.resolveUnEncryptedJobConfiguration(job); + + /* test */ + assertThat(result).isNotNull(); + assertThat(result.toJSON()).isEqualTo(jsonAsPlainText); + + } + + @Test + void access_throws_pds_encryption_exception_when_job_has_encrypted_data_but_encryption_service_throws_an_exception() { + /* prepare */ + PDSJob job = mock(PDSJob.class); + when(encryptionService.decryptString(any(), any())).thenThrow(new TestCanaryException()); + when(job.getEncryptedConfiguration()).thenReturn("something".getBytes()); + when(job.getEncryptionInitialVectorData()).thenReturn("init".getBytes()); + + /* execute */ + assertThatThrownBy(() -> accessToTest.resolveUnEncryptedJobConfiguration(job)).isInstanceOf(PDSEncryptionException.class) + .hasRootCauseInstanceOf(TestCanaryException.class); + } + + @Test + void access_throws_pds_encryption_exception_when_job_has_NO_encrypted_data() { + /* prepare */ + PDSJob job = mock(PDSJob.class); + UUID uuid = UUID.randomUUID(); + when(job.getUUID()).thenReturn(uuid); + when(job.getEncryptedConfiguration()).thenReturn(null); + when(job.getEncryptionInitialVectorData()).thenReturn("init".getBytes()); + + /* execute */ + assertThatThrownBy(() -> accessToTest.resolveUnEncryptedJobConfiguration(job)).isInstanceOf(PDSEncryptionException.class) + .hasRootCauseInstanceOf(IllegalStateException.class).hasRootCauseMessage("No encrypted configuration found for PDS job: " + uuid); + } + + @Test + void access_throws_pds_encryption_exception_when_job_has_NO_initial_vector_data() { + /* prepare */ + PDSJob job = mock(PDSJob.class); + UUID uuid = UUID.randomUUID(); + when(job.getUUID()).thenReturn(uuid); + when(job.getEncryptedConfiguration()).thenReturn("config".getBytes()); + when(job.getEncryptionInitialVectorData()).thenReturn(null); + + /* execute */ + assertThatThrownBy(() -> accessToTest.resolveUnEncryptedJobConfiguration(job)).isInstanceOf(PDSEncryptionException.class) + .hasRootCauseInstanceOf(IllegalStateException.class).hasRootCauseMessage("No initial vector data found for PDS job: " + uuid); + } + +} diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidatorTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidatorTest.java index 8dccbc8190..8a93f61ea2 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidatorTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationValidatorTest.java @@ -96,7 +96,7 @@ void when_a_server_configuration_does_not_contain_product_id_an_exception_is_thr /* execute + test */ PDSNotAcceptableException exception = assertThrows(PDSNotAcceptableException.class, () -> validatorToTest.assertPDSConfigurationValid(config)); String message = exception.getMessage(); - assertTrue(message.contains("does not support product identifier")); + assertTrue(message.contains("does not encryptionSupport product identifier")); } @Test diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRepositoryDBTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRepositoryDBTest.java index b616d56f97..264f536276 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRepositoryDBTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRepositoryDBTest.java @@ -495,7 +495,8 @@ private PDSJob createJob(PDSJobStatusState state, int minutes, String serverId) // necessary because must be not null job.created = LocalDateTime.of(2020, 06, 24, 13, 55, 01).minusMinutes(minutes); job.owner = "owner"; - job.jsonConfiguration = "{}"; + job.encryptedConfiguration = "{}".getBytes(); // simulate encryption + job.encryptionInitialVectorData = "initial-vector".getBytes(); // simulate initial vector job.state = state; /* persist */ diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRestControllerMockTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRestControllerMockTest.java index c00fc3a9ad..0fa03b5fa6 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRestControllerMockTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSJobRestControllerMockTest.java @@ -1,19 +1,13 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.pds.job; -import static com.mercedesbenz.sechub.test.PDSTestURLBuilder.https; -import static com.mercedesbenz.sechub.test.TestConstants.SOURCECODE_ZIP; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static com.mercedesbenz.sechub.test.PDSTestURLBuilder.*; +import static com.mercedesbenz.sechub.test.TestConstants.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.util.UUID; @@ -34,6 +28,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatus; +import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; import com.mercedesbenz.sechub.pds.commons.core.PDSProfiles; import com.mercedesbenz.sechub.pds.security.PDSAPISecurityConfiguration; import com.mercedesbenz.sechub.pds.security.PDSRoleConstants; @@ -114,11 +111,11 @@ public void a_get_job_status_call_calls_status_service_and_returns_status_as_JSO UUID jobUUID = UUID.randomUUID(); PDSJobStatus status = new PDSJobStatus(); - status.created = "created1"; - status.ended = "ended1"; - status.jobUUID = jobUUID; - status.owner = "owner1"; - status.state = "state1"; + status.setCreated("created1"); + status.setEnded("ended1"); + status.setJobUUID(jobUUID); + status.setOwner("owner1"); + status.setState(PDSJobStatusState.RUNNING); when(mockedJobStatusService.getJobStatus(jobUUID)).thenReturn(status); @@ -128,7 +125,7 @@ public void a_get_job_status_call_calls_status_service_and_returns_status_as_JSO get(https(PORT_USED).buildGetJobStatus(jobUUID)) ). andExpect(status().isOk()). - andExpect(content().json(status.toJSON(),true) + andExpect(content().json(JSONConverter.get().toJSON(status),true) ); /* @formatter:on */ diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java index 405f97da6b..91049320fa 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java @@ -169,7 +169,7 @@ void when_job_has_metadata_a_metadata_file_is_created_in_workspace_containing_co /* test */ File metaDataFile = serviceToTest.getMetaDataFile(jobUUID); assertTrue(metaDataFile.exists()); - assertEquals("this is my metadata", TestFileReader.loadTextFile(metaDataFile)); + assertEquals("this is my metadata", TestFileReader.readTextFromFile(metaDataFile)); } @Test diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/test/ExtendedMockMultipartFile.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/test/ExtendedMockMultipartFile.java deleted file mode 100644 index 56d6609674..0000000000 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/test/ExtendedMockMultipartFile.java +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.pds.test; - -import java.io.IOException; -import java.io.InputStream; - -import org.springframework.mock.web.MockMultipartFile; - -/** - * Based on {@link MockMultipartFile} - with some extensions: - *
      - *
    1. remember last input stream fetched by {@link #getInputStream()}. This - * stream object can be fetched by {@link #getRememberedInputStream()}. So - * mockito tests can verify input stream parameters
    2. - *
    - * - * @author Albert Tregnaghi - * - */ -public class ExtendedMockMultipartFile extends MockMultipartFile { - - private InputStream rememberedInputStream; - - public ExtendedMockMultipartFile(String name, byte[] content) { - super(name, content); - } - - @Override - public InputStream getInputStream() throws IOException { - rememberedInputStream = super.getInputStream(); - return rememberedInputStream; - } - - public InputStream getRememberedInputStream() { - return rememberedInputStream; - } - -} \ No newline at end of file diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSResilienceConsultant.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSResilienceConsultant.java index 630120b1cf..d610a87f50 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSResilienceConsultant.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSResilienceConsultant.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; +import com.mercedesbenz.sechub.adapter.pds.PDSEncryptionOutOfSyncException; import com.mercedesbenz.sechub.commons.core.resilience.ResilienceConsultant; import com.mercedesbenz.sechub.commons.core.resilience.ResilienceContext; import com.mercedesbenz.sechub.commons.core.resilience.ResilienceProposal; @@ -20,11 +21,23 @@ public class PDSResilienceConsultant implements ResilienceConsultant { private static final Logger LOG = LoggerFactory.getLogger(PDSResilienceConsultant.class); + private static final int DEFAULT_ENCRYPTION_OUT_OF_SYNC_RETRY_MAX = 3; + private static final int DEFAULT_ENCRYPTION_OUT_OF_SYNC_RETRY_TIMEOUT_MILLISECONDS = 2000; + private static final int DEFAULT_BADREQUEST_RETRY_MAX = 3; private static final int DEFAULT_BADREQUEST_RETRY_TIMEOUT_MILLISECONDS = 2000; + private static final int DEFAULT_SERVERERROR_RETRY_MAX = 1; private static final int DEFAULT_SERVERERROR_RETRY_TIMEOUT_MILLISECONDS = 5000; + @Value("${sechub.adapter.pds.resilience.encryption-out-of-sync.retry.max:" + DEFAULT_ENCRYPTION_OUT_OF_SYNC_RETRY_MAX + "}") + @MustBeDocumented("Amount of retries done when a PDS encryption out of sync problem happens") + private int encryptionOutOfSyncMaxRetries = DEFAULT_ENCRYPTION_OUT_OF_SYNC_RETRY_MAX; + + @Value("${sechub.adapter.pds.resilience.encryption-out-of-sync.retry.wait:" + DEFAULT_ENCRYPTION_OUT_OF_SYNC_RETRY_TIMEOUT_MILLISECONDS + "}") + @MustBeDocumented("Time to wait until retry is done when a PDS encryption out of sync problem happens") + private int encryptionOutOfSyncMRetryTimeToWaitInMilliseconds = DEFAULT_ENCRYPTION_OUT_OF_SYNC_RETRY_TIMEOUT_MILLISECONDS; + @Value("${sechub.adapter.checkmarx.resilience.badrequest.retry.max:" + DEFAULT_BADREQUEST_RETRY_MAX + "}") @MustBeDocumented("Amount of retries done when a 400 bad request happened on Checkmarx server") private int badRequestMaxRetries = DEFAULT_BADREQUEST_RETRY_MAX; @@ -45,16 +58,22 @@ public class PDSResilienceConsultant implements ResilienceConsultant { public ResilienceProposal consultFor(ResilienceContext context) { Objects.requireNonNull(context); Throwable rootCause = StacktraceUtil.findRootCause(context.getCurrentError()); + if (rootCause instanceof PDSEncryptionOutOfSyncException) { + LOG.info("Propose retry for PDS encryption out of sync"); + return new SimpleRetryResilienceProposal("Encryption out of sync handling", encryptionOutOfSyncMaxRetries, + encryptionOutOfSyncMRetryTimeToWaitInMilliseconds); + } + if (rootCause instanceof HttpClientErrorException) { HttpClientErrorException hce = (HttpClientErrorException) rootCause; - int statusCode = hce.getRawStatusCode(); + int statusCode = hce.getStatusCode().value(); if (statusCode == 400) { /* * BAD request - this can happen for same project scans put to queue because * there can a CHECKMARX server error happen */ LOG.info("Propose retry for bad request"); - return new SimpleRetryResilienceProposal("checkmarx bad request handling", badRequestMaxRetries, badRequestRetryTimeToWaitInMilliseconds); + return new SimpleRetryResilienceProposal("bad request handling", badRequestMaxRetries, badRequestRetryTimeToWaitInMilliseconds); } else if (statusCode == 500) { /* diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java index edce427422..ebb18ba623 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java @@ -41,6 +41,7 @@ import com.mercedesbenz.sechub.sharedkernel.APIConstants; import com.mercedesbenz.sechub.sharedkernel.ProductIdentifier; import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.mapping.MappingIdentifier; /** * Contains additional rest call functionality for integration tests on scan @@ -218,7 +219,8 @@ public MappingData fetchScanMappingData(@PathVariable("mappingId") String mappin @SuppressWarnings("deprecation") @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/config/namepattern/{namePatternProviderId}/{name}", method = RequestMethod.GET) public String getIdForNameByProvider(@PathVariable("namePatternProviderId") String namePatternProviderId, @PathVariable("name") String name) { - NamePatternIdProvider provider = scanMappingConfigurationService.getNamePatternIdProvider(namePatternProviderId); + MappingIdentifier mappingIdentifier = MappingIdentifier.valueOf(namePatternProviderId); + NamePatternIdProvider provider = scanMappingConfigurationService.getNamePatternIdProvider(mappingIdentifier); if (provider == null) { return null; } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java index efed688e84..266412acf0 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java @@ -186,7 +186,7 @@ private SecHubExecutionContext createExecutionContext(DomainMessage message) thr UUID sechubJobUUID = message.get(SECHUB_JOB_UUID); String executedBy = message.get(EXECUTED_BY); - SecHubConfiguration configuration = message.get(SECHUB_CONFIG); + SecHubConfiguration configuration = message.get(SECHUB_UNENCRYPTED_CONFIG); if (configuration == null) { throw new IllegalStateException("SecHubConfiguration not found in message - so cannot execute!"); } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/config/ScanMappingConfigurationService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/config/ScanMappingConfigurationService.java index 962a7ff570..ae1ff3863e 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/config/ScanMappingConfigurationService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/config/ScanMappingConfigurationService.java @@ -57,15 +57,7 @@ public NamePatternIdProvider getNamePatternIdProvider(MappingIdentifier identifi return getNamePatternIdProvider(identifier.getId()); } - /** - * Get provider to resolve IDs by a given name. Deprecation: This method should - * NOT be used outside this package to avoid usage without mapping identifiers! - * - * @param namePatternMappingId - * @return provider, never null - */ - @Deprecated - public NamePatternIdProvider getNamePatternIdProvider(String namePatternMappingId) { + NamePatternIdProvider getNamePatternIdProvider(String namePatternMappingId) { synchronized (providers) { NamePatternIdProvider provider = providers.get(namePatternMappingId); if (provider != null) { diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLog.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLog.java index 5421d14a78..bb2a7f485b 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLog.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLog.java @@ -1,13 +1,11 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.log; -import java.sql.Types; import java.time.LocalDateTime; import java.util.Objects; import java.util.UUID; import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.JdbcTypeCode; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; @@ -44,7 +42,6 @@ public class ProjectScanLog { public static final String COLUMN_PROJECT_ID = "PROJECT_ID"; public static final String COLUMN_EXECUTED_BY = "EXECUTED_BY"; public static final String COLUMN_SECHUB_JOB_UUID = "SECHUB_JOB_UUID"; - public static final String COLUMN_CONFIG = "CONFIG"; public static final String COLUMN_STATUS = "STATUS"; public static final String COLUMN_STARTED = "STARTED"; @@ -83,10 +80,6 @@ public class ProjectScanLog { @Column(name = COLUMN_SECHUB_JOB_UUID, nullable = false, columnDefinition = "UUID") UUID sechubJobUUID; - @JdbcTypeCode(Types.LONGVARCHAR) - @Column(name = COLUMN_CONFIG) - String config; - @Column(name = COLUMN_STATUS) String status; @@ -108,11 +101,10 @@ public class ProjectScanLog { // jpa only } - public ProjectScanLog(String projectId, UUID sechubJobUUID, String executedBy, String config) { + public ProjectScanLog(String projectId, UUID sechubJobUUID, String executedBy) { this.projectId = projectId; this.sechubJobUUID = sechubJobUUID; this.executedBy = executedBy; - this.config = config; this.started = LocalDateTime.now(); } @@ -121,10 +113,6 @@ public void setStatus(String status) { this.status = status; } - public String getConfig() { - return config; - } - public LocalDateTime getStarted() { return started; } @@ -151,7 +139,7 @@ public String getExecutedBy() { @Override public int hashCode() { - return Objects.hash(config, ended, executedBy, projectId, sechubJobUUID, started, uUID, version); + return Objects.hash(ended, executedBy, projectId, sechubJobUUID, started, uUID, version); } @Override @@ -163,15 +151,15 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; ProjectScanLog other = (ProjectScanLog) obj; - return Objects.equals(config, other.config) && Objects.equals(ended, other.ended) && Objects.equals(executedBy, other.executedBy) - && Objects.equals(projectId, other.projectId) && Objects.equals(sechubJobUUID, other.sechubJobUUID) && Objects.equals(started, other.started) - && Objects.equals(uUID, other.uUID) && Objects.equals(version, other.version); + return Objects.equals(ended, other.ended) && Objects.equals(executedBy, other.executedBy) && Objects.equals(projectId, other.projectId) + && Objects.equals(sechubJobUUID, other.sechubJobUUID) && Objects.equals(started, other.started) && Objects.equals(uUID, other.uUID) + && Objects.equals(version, other.version); } @Override public String toString() { return "ProjectScanLog [\nuUID=" + uUID + ", \nexecutedBy=" + executedBy + ", \nprojectId=" + projectId + ", \nsechubJobUUID=" + sechubJobUUID - + ", \nstatus=" + status + ", \nstarted=" + started + ", \nended=" + ended + ", \nconfig=" + config + "\n]"; + + ", \nstatus=" + status + ", \nstarted=" + started + ", \nended=" + ended + "\n]"; } } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogService.java index 38d5476e28..26ace64bd5 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogService.java @@ -32,10 +32,9 @@ public class ProjectScanLogService { public UUID logScanStarted(SecHubExecutionContext context) { String projectId = context.getConfiguration().getProjectId(); UUID secHubJobUUID = context.getSechubJobUUID(); - String config = context.getConfiguration().toJSON(); String executedBy = context.getExecutedBy(); - ProjectScanLog log = new ProjectScanLog(projectId, secHubJobUUID, executedBy, config); + ProjectScanLog log = new ProjectScanLog(projectId, secHubJobUUID, executedBy); log.setStatus(ProjectScanLog.STATUS_STARTED); ProjectScanLog persistedLog = repository.save(log); return persistedLog.getUUID(); diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanServiceTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanServiceTest.java index 852b67cb43..fc181cc6c6 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanServiceTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/ScanServiceTest.java @@ -257,7 +257,7 @@ public void event_handling_works_as_expected_and_SCAN_DONE_is_returned_as_result public void event_handling_FAILED_when_configuration_is_not_set() { /* prepare */ DomainMessage request = prepareValidRequest(); - request.set(MessageDataKeys.SECHUB_CONFIG, null); + request.set(MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG, null); /* execute */ DomainMessageSynchronousResult result = simulateEventSend(request, serviceToTest); @@ -329,7 +329,7 @@ private DomainMessage prepareRequest(SecHubConfiguration configMin) { DomainMessage request = new DomainMessage(MessageID.START_SCAN); request.set(MessageDataKeys.SECHUB_JOB_UUID, SECHUB_JOB_UUID); request.set(MessageDataKeys.SECHUB_EXECUTION_UUID, EXECUTION_UUID); - request.set(MessageDataKeys.SECHUB_CONFIG, configMin); + request.set(MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG, configMin); return request; } diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/admin/FullScanDataToZipOutputSupportTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/admin/FullScanDataToZipOutputSupportTest.java index dd6f18ec79..c7c40bc2ae 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/admin/FullScanDataToZipOutputSupportTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/admin/FullScanDataToZipOutputSupportTest.java @@ -25,6 +25,8 @@ public class FullScanDataToZipOutputSupportTest { + private static final String EXECUTOR1 = "executor1"; + private static final String PROJECT1 = "project1"; private FullScanDataToZipOutputSupport supportToTest; private UUID sechubJobUUID; @@ -59,7 +61,9 @@ public void writeScanDataContainsDataAsExpected() throws Exception { assertTrue(data1.content.contains("OK")); assertTrue(data2.content.contains("NOT-OK")); - assertTrue(data3.content.contains("'heavy'")); + assertTrue(data3.content.contains("sechubJobUUID=" + sechubJobUUID)); + assertTrue(data3.content.contains("projectId=project1")); + assertTrue(data3.content.contains("executedBy=" + EXECUTOR1)); assertTrue(data4.content.contains("metadata")); assertTrue(data4.content.contains("product1")); @@ -126,7 +130,7 @@ private FullScanData createFullScanDataTwoProductsOneLogEntry() { fullScanData.allScanData.add(data1); fullScanData.allScanData.add(data2); - ProjectScanLog log1 = new ProjectScanLog("project1", sechubJobUUID, "executor1", "{'config':'heavy'}"); + ProjectScanLog log1 = new ProjectScanLog(PROJECT1, sechubJobUUID, EXECUTOR1); fullScanData.allScanLogs.add(log1); return fullScanData; } diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/analytic/ClocJsonAnalyticDataImporterTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/analytic/ClocJsonAnalyticDataImporterTest.java index 61fbe121fc..7832d70511 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/analytic/ClocJsonAnalyticDataImporterTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/analytic/ClocJsonAnalyticDataImporterTest.java @@ -25,8 +25,8 @@ class ClocJsonAnalyticDataImporterTest { @BeforeAll static void beforeAll() { - sechubClocJSON = TestFileReader.loadTextFile(new File("./src/test/resources/cloc/cloc-sechub.json")); - gosecClocJSON = TestFileReader.loadTextFile(new File("./src/test/resources/cloc/cloc-gosec.json")); + sechubClocJSON = TestFileReader.readTextFromFile(new File("./src/test/resources/cloc/cloc-sechub.json")); + gosecClocJSON = TestFileReader.readTextFromFile(new File("./src/test/resources/cloc/cloc-gosec.json")); } @BeforeEach diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogRepositoryDBTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogRepositoryDBTest.java index 661f77613d..dbe8e03e20 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogRepositoryDBTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/log/ProjectScanLogRepositoryDBTest.java @@ -256,7 +256,7 @@ private ProjectScanLog create(LocalDateTime since) { } private ProjectScanLog createNewProjectScanLog(String projectId) { - return new ProjectScanLog(projectId, UUID.randomUUID(), "testuser", "{}"); + return new ProjectScanLog(projectId, UUID.randomUUID(), "testuser"); } @TestConfiguration diff --git a/sechub-schedule/build.gradle b/sechub-schedule/build.gradle index 8428acda80..4dbe2c0cdf 100644 --- a/sechub-schedule/build.gradle +++ b/sechub-schedule/build.gradle @@ -12,6 +12,7 @@ dependencies { implementation(library.apache_commons_fileupload2_jakarta) implementation project(':sechub-shared-kernel') + implementation project(':sechub-commons-encryption') testImplementation project(':sechub-testframework') testImplementation project(':sechub-commons-model-testframework') diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java index 8a1209e169..e5ad91ea10 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerRestController.java @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.schedule; +import java.util.Optional; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.http.MediaType; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -13,6 +15,9 @@ import com.mercedesbenz.sechub.domain.schedule.access.ScheduleAccessCountService; import com.mercedesbenz.sechub.domain.schedule.config.SchedulerConfigService; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleCipherPoolCleanupService; +import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; import com.mercedesbenz.sechub.domain.schedule.strategy.SchedulerStrategyFactory; import com.mercedesbenz.sechub.sharedkernel.APIConstants; import com.mercedesbenz.sechub.sharedkernel.Profiles; @@ -40,12 +45,24 @@ public class IntegrationTestSchedulerRestController { @Autowired private SchedulerConfigService scheduleConfigService; + @Autowired + private SecHubJobRepository jobRepository; + + @Autowired + private ScheduleCipherPoolCleanupService scheduleCipherPoolCleanupService; + @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/autocleanup/inspection/schedule/days", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE }) public long fetchScheduleAutoCleanupConfiguredDays() { return scheduleConfigService.getAutoCleanupInDays(); } + @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/schedule/cipher-pool-data/cleanup", method = RequestMethod.PUT, produces = { + MediaType.APPLICATION_JSON_VALUE }) + public void startScheduleAutoCleanupDirectlyForTesting() { + scheduleCipherPoolCleanupService.cleanupCipherPoolDataIfNecessaryAndPossible(); + } + @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/project/{projectId}/schedule/access/count", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE }) public long countProjectAccess(@PathVariable("projectId") String projectId) { @@ -61,7 +78,6 @@ public void deleteWaitingJobs() { @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/schedule/revert/job/{sechubJobUUID}/still-running", method = RequestMethod.PUT, produces = { MediaType.APPLICATION_JSON_VALUE }) public void revertJobAsStillRunning(@PathVariable("sechubJobUUID") UUID sechubJobUUID) { - ; integrationTestSchedulerService.revertJobAsStillRunning(sechubJobUUID); } @@ -69,7 +85,6 @@ public void revertJobAsStillRunning(@PathVariable("sechubJobUUID") UUID sechubJo + "integrationtest/schedule/revert/job/{sechubJobUUID}/still-not-approved", method = RequestMethod.PUT, produces = { MediaType.APPLICATION_JSON_VALUE }) public void revertJobAsStillNotApproved(@PathVariable("sechubJobUUID") UUID sechubJobUUID) { - ; integrationTestSchedulerService.revertJobAsStillNotApproved(sechubJobUUID); } @@ -79,4 +94,15 @@ public void setSchedulerStrategy(@PathVariable("strategyId") String strategyId) schedulerStrategyFactory.setStrategyIdentifier(strategyId); } + @RequestMapping(path = APIConstants.API_ANONYMOUS + + "integrationtest/schedule/encryption-pool-id/job/{sechubJobUUID}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE }) + @Transactional + public Long fetchEncryptionPoolIdForSecHubJob(@PathVariable("sechubJobUUID") UUID sechubJobUUID) { + Optional job = jobRepository.findById(sechubJobUUID); + if (job.isEmpty()) { + throw new IllegalArgumentException("SecHub job: " + sechubJobUUID + " not found!"); + } + return job.get().getEncryptionCipherPoolId(); + } + } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerService.java index 74bf15dd12..a53ce32135 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/IntegrationTestSchedulerService.java @@ -40,7 +40,7 @@ public class IntegrationTestSchedulerService { */ public void deleteWaitingJobs() { /* - * we do not add the query to the repository, because it is not used in + * we do not add the query to the poolDataRepository, because it is not used in * production but only for testing */ Query query = entityManager.createQuery(DELETE_WAITING_JOBS_QUERY); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobLauncherService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobLauncherService.java index bea8cf14bb..bbf78acded 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobLauncherService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleJobLauncherService.java @@ -52,17 +52,16 @@ public void executeJob(ScheduleSecHubJob secHubJob) { executor.execute(secHubJob); /* send domain event */ - sendJobStarted(secHubJob.getProjectId(), secHubJobUUID, secHubJob.getJsonConfiguration(), secHubJob.getOwner()); + sendJobStarted(secHubJob.getProjectId(), secHubJobUUID, secHubJob.getOwner()); } @IsSendingAsyncMessage(MessageID.JOB_STARTED) - private void sendJobStarted(String projectId, UUID jobUUID, String configuration, String owner) { + private void sendJobStarted(String projectId, UUID jobUUID, String owner) { DomainMessage request = new DomainMessage(MessageID.JOB_STARTED); JobMessage message = new JobMessage(); message.setProjectId(projectId); message.setJobUUID(jobUUID); - message.setConfiguration(configuration); message.setOwner(owner); message.setSince(LocalDateTime.now()); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleMessageHandler.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleMessageHandler.java index be653f5c45..fdd91bf9ef 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleMessageHandler.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleMessageHandler.java @@ -15,10 +15,13 @@ import com.mercedesbenz.sechub.domain.schedule.access.ScheduleRevokeUserAccessFromProjectService; import com.mercedesbenz.sechub.domain.schedule.config.SchedulerConfigService; import com.mercedesbenz.sechub.domain.schedule.config.SchedulerProjectConfigService; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionRotationService; +import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJobEncryptionUpdateService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobTransactionService; import com.mercedesbenz.sechub.domain.schedule.status.SchedulerStatusService; import com.mercedesbenz.sechub.domain.schedule.whitelist.ProjectWhiteListUpdateService; import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; import com.mercedesbenz.sechub.sharedkernel.messaging.AdministrationConfigMessage; import com.mercedesbenz.sechub.sharedkernel.messaging.AsynchronMessageHandler; import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; @@ -69,10 +72,17 @@ public class ScheduleMessageHandler implements AsynchronMessageHandler { @Autowired SecHubJobTransactionService jobTransactionService; + @Autowired + ScheduleEncryptionRotationService encryptionRotatonService; + + @Autowired + ScheduleSecHubJobEncryptionUpdateService encryptionUpdateService; + @Override public void receiveAsyncMessage(DomainMessage request) { + LOG.debug("received asynchronous domain request: {}", request); + MessageID messageId = request.getMessageId(); - LOG.debug("received domain request: {}", request); switch (messageId) { case USER_ADDED_TO_PROJECT: @@ -120,6 +130,12 @@ public void receiveAsyncMessage(DomainMessage request) { case PRODUCT_EXECUTOR_CANCEL_OPERATIONS_DONE: handleProductExecutorCancelOperationsDone(request); break; + case START_ENCRYPTION_ROTATION: + handleEncryptionRotation(request); + break; + case SCHEDULE_ENCRYPTION_POOL_INITIALIZED: + handleEncryptionPoolInitialized(request); + break; default: throw new IllegalStateException("unhandled message id:" + messageId); } @@ -230,6 +246,19 @@ private void handleProjectDeleted(DomainMessage request) { projectConfigService.deleteProjectConfiguration(projectId); } + @IsReceivingAsyncMessage(MessageID.START_ENCRYPTION_ROTATION) + private void handleEncryptionRotation(DomainMessage request) { + SecHubEncryptionData data = request.get(MessageDataKeys.SECHUB_ENCRYPT_ROTATION_DATA); + String executedBy = request.get(MessageDataKeys.EXECUTED_BY); + + encryptionRotatonService.startEncryptionRotation(data, executedBy); + } + + @IsReceivingAsyncMessage(MessageID.SCHEDULE_ENCRYPTION_POOL_INITIALIZED) + private void handleEncryptionPoolInitialized(DomainMessage request) { + encryptionUpdateService.updateEncryptedDataIfNecessary(); + } + private void updateWhiteList(ProjectMessage data) { projectWhiteListUpdateService.update(data.getProjectId(), data.getWhitelist()); } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleShutdownService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleShutdownService.java new file mode 100644 index 0000000000..34efb24c62 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/ScheduleShutdownService.java @@ -0,0 +1,36 @@ +package com.mercedesbenz.sechub.domain.schedule; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Service; + +@Service +public class ScheduleShutdownService implements ApplicationContextAware { + + private static final Logger LOG = LoggerFactory.getLogger(ScheduleShutdownService.class); + + private ApplicationContext context; + + public void shutdownApplication() { + if (context instanceof ConfigurableApplicationContext) { + LOG.info("Will now trigger shutdown of application context"); + ((ConfigurableApplicationContext) context).close(); + } else { + if (context == null) { + LOG.error("Cannot shutdown application context because context null!"); + } else { + LOG.error("Cannot shutdown application context because wrong context:" + context.getClass()); + } + } + } + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + this.context = ctx; + + } +} \ No newline at end of file diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerBinariesUploadConfiguration.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerBinariesUploadConfiguration.java index 412436dc8c..4c400c2855 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerBinariesUploadConfiguration.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerBinariesUploadConfiguration.java @@ -4,11 +4,11 @@ import static com.mercedesbenz.sechub.commons.core.CommonConstants.*; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; -@Service +@Component public class SchedulerBinariesUploadConfiguration { private static final long DEFAULT_MAX_UPLOAD_SIZE_IN_BYTES = 50 * 1024 * 1024; // 50 MiB diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java index 409079f820..f64915e849 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerJobBatchTriggerService.java @@ -48,8 +48,8 @@ public class SchedulerJobBatchTriggerService { @MustBeDocumented("When retry mechanism is enabled by `sechub.config.trigger.nextjob.retries`, and a retry is necessary, " + "this value is used to define the maximum time period in millis which will be waited before retry. " + "Why max value? Because cluster instances seems to be created often on exact same time by kubernetes. " - + "So having here a max value will result in a randomized wait time so cluster members will do " - + "fetch operations time shifted and automatically reduce collisions!") + + "So having here a max value will result in a randomized wait time: means cluster members will do " + + "fetch operations time shifted and this automatically reduces collisions!") @Value("${sechub.config.trigger.nextjob.maxwaitretry:" + DEFAULT_RETRY_MAX_MILLIS + "}") private int markNextJobWaitBeforeRetryMillis = DEFAULT_RETRY_MAX_MILLIS; @@ -61,7 +61,7 @@ public class SchedulerJobBatchTriggerService { @Value("${sechub.config.trigger.nextjob.delay:" + DEFAULT_FIXED_DELAY_MILLIS + "}") private String infoFixedDelay; // here only for logging - used in scheduler annotation as well! - @MustBeDocumented("When enabled each trigger will do an healtching by monitoring service. If system has too much CPU load or uses too much memory, the trigger will not execute until memory and CPU load is at normal level!") + @MustBeDocumented("When enabled each trigger will do an health check by monitoring service. If system has too much CPU load or uses too much memory, the trigger will not execute until memory and CPU load is at normal level!") @Value("${sechub.config.trigger.healthcheck.enabled:" + DEFAULT_HEALTHCHECK_ENABLED + "}") private boolean healthCheckEnabled = DEFAULT_HEALTHCHECK_ENABLED; @@ -143,6 +143,7 @@ public void triggerExecutionOfNextJob() { retryContext.markAsFatalFailure(); } + } while (retryContext.isRetryPossible()); if (!retryContext.isExecutionDone()) { diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadConfiguration.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadConfiguration.java index 32af4c1e90..4c6360341a 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadConfiguration.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadConfiguration.java @@ -2,11 +2,11 @@ package com.mercedesbenz.sechub.domain.schedule; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; -@Service +@Component public class SchedulerSourcecodeUploadConfiguration { @Value("${sechub.server.upload.validate.zip:true}") diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadService.java index d5ebe14f99..9685c98c38 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSourcecodeUploadService.java @@ -196,7 +196,6 @@ private void assertCheckSumCorrect(String checkSum, InputStream inputStream) { } } - @SuppressWarnings("deprecation") private void assertValidZipFile(InputStream inputStream) { if (!archiveSupportProvider.getArchiveSupport().isZipFileStream(inputStream)) { LOG.error("Uploaded file is NOT a valid ZIP file!"); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupService.java index 7ab2093e48..695a05fa4c 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupService.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service; import com.mercedesbenz.sechub.domain.schedule.config.SchedulerConfigService; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleCipherPoolCleanupService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobDataRepository; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; import com.mercedesbenz.sechub.sharedkernel.Step; @@ -37,6 +38,9 @@ public class ScheduleAutoCleanupService { @Autowired AutoCleanupResultInspector inspector; + @Autowired + ScheduleCipherPoolCleanupService encryptionPoolCleanupService; + @UseCaseScheduleAutoCleanExecution(@Step(number = 2, name = "Delete old data", description = "deletes old job information")) public void cleanup() { /* calculate */ @@ -61,6 +65,9 @@ public void cleanup() { ); /* @formatter:on */ + /* cleanup encryption */ + encryptionPoolCleanupService.cleanupCipherPoolDataIfNecessaryAndPossible(); + } } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/batch/SynchronSecHubJobExecutor.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/batch/SynchronSecHubJobExecutor.java index 490c326957..ca73087e43 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/batch/SynchronSecHubJobExecutor.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/batch/SynchronSecHubJobExecutor.java @@ -20,6 +20,7 @@ import com.mercedesbenz.sechub.commons.model.job.ExecutionResult; import com.mercedesbenz.sechub.domain.schedule.UUIDContainer; import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubConfigurationModelAccessService; import com.mercedesbenz.sechub.sharedkernel.LogConstants; import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; @@ -54,6 +55,9 @@ public class SynchronSecHubJobExecutor { @Autowired SecHubJobSafeUpdater secHubJobSafeUpdater; + @Autowired + SecHubConfigurationModelAccessService configurationModelAccess; + @IsSendingSyncMessage(MessageID.START_SCAN) public void execute(final ScheduleSecHubJob secHubJob) { Thread scheduleWorkerThread = new Thread(() -> executeInsideThread(secHubJob), SECHUB_SCHEDULE_THREAD_PREFIX + secHubJob.getUUID()); @@ -66,7 +70,7 @@ private void executeInsideThread(final ScheduleSecHubJob secHubJob) { uuids.setSecHubJobUUID(secHubJob.getUUID()); try { - String secHubConfiguration = secHubJob.getJsonConfiguration(); + String secHubConfiguration = configurationModelAccess.resolveUnencryptedConfigurationasJson(secHubJob); /* own thread so MDC.put necessary */ MDC.clear(); @@ -76,18 +80,18 @@ private void executeInsideThread(final ScheduleSecHubJob secHubJob) { LOG.info("Executing sechub job: {}, execution uuid: {}", uuids.getSecHubJobUUIDasString(), uuids.getExecutionUUIDAsString()); - sendJobExecutionStartingEvent(secHubJob, uuids, secHubConfiguration); + sendJobExecutionStartingEvent(secHubJob, uuids); /* we send now a synchronous SCAN event */ - DomainMessage request = new DomainMessage(MessageID.START_SCAN); - request.set(MessageDataKeys.SECHUB_EXECUTION_UUID, uuids.getExecutionUUID()); - request.set(MessageDataKeys.EXECUTED_BY, secHubJob.getOwner()); + DomainMessage startScanRequest = new DomainMessage(MessageID.START_SCAN); + startScanRequest.set(MessageDataKeys.SECHUB_EXECUTION_UUID, uuids.getExecutionUUID()); + startScanRequest.set(MessageDataKeys.EXECUTED_BY, secHubJob.getOwner()); - request.set(MessageDataKeys.SECHUB_JOB_UUID, uuids.getSecHubJobUUID()); - request.set(MessageDataKeys.SECHUB_CONFIG, MessageDataKeys.SECHUB_CONFIG.getProvider().get(secHubConfiguration)); + startScanRequest.set(MessageDataKeys.SECHUB_JOB_UUID, uuids.getSecHubJobUUID()); + startScanRequest.set(MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG, MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG.getProvider().get(secHubConfiguration)); /* wait for scan event result - synchron */ - DomainMessageSynchronousResult response = messageService.sendSynchron(request); + DomainMessageSynchronousResult response = messageService.sendSynchron(startScanRequest); updateSecHubJob(uuids, response); @@ -106,7 +110,7 @@ private void executeInsideThread(final ScheduleSecHubJob secHubJob) { } @IsSendingAsyncMessage(MessageID.JOB_EXECUTION_STARTING) - private void sendJobExecutionStartingEvent(final ScheduleSecHubJob secHubJob, UUIDContainer uuids, String secHubConfiguration) { + private void sendJobExecutionStartingEvent(final ScheduleSecHubJob secHubJob, UUIDContainer uuids) { /* we send asynchronous an information event */ DomainMessage jobExecRequest = new DomainMessage(MessageID.JOB_EXECUTION_STARTING); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupService.java new file mode 100644 index 0000000000..f1e935b0c8 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupService.java @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.autocleanup.UseCaseScheduleAutoCleanExecution; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseEncryptionCleanup; + +@Service +public class ScheduleCipherPoolCleanupService { + + private static final String DESCRIPTION = "Removes cipher pool data entries from database which are no longer used by any job"; + + private static final Logger LOG = LoggerFactory.getLogger(ScheduleCipherPoolCleanupService.class); + + @Autowired + ScheduleEncryptionService encryptionService; + + @Autowired + ScheduleLatestCipherPoolDataCalculator latestCipherPoolDataCalculator; + + @Autowired + ScheduleCipherPoolDataRepository poolDataRepository; + + @Autowired + SecHubJobRepository jobRepository; + + @Autowired + SecHubOutdatedEncryptionPoolSupport outdatedEncryptionPoolSupport; + + @UseCaseEncryptionCleanup(@Step(number = 1, name = "Schedule cipher pool data cleanup", description = DESCRIPTION)) + @UseCaseScheduleAutoCleanExecution(@Step(number = 3, name = "Schedule cipher pool data cleanup", description = DESCRIPTION)) + public void cleanupCipherPoolDataIfNecessaryAndPossible() { + LOG.debug("Encryption pool cleanup check"); + + /* check clean up possible */ + if (outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()) { + LOG.debug("It is stil possible to have outdated encryption pools, cannot cleanup"); + return; + } + + /* resolve data */ + List allPoolData = poolDataRepository.findAll(); + if (allPoolData.isEmpty()) { + LOG.warn("No pool data found in database, cannot do encryption cleanup"); + return; + } + ScheduleCipherPoolData latestPoolDataFromDatabase = latestCipherPoolDataCalculator.calculateLatestPoolData(allPoolData); + if (latestPoolDataFromDatabase == null) { + LOG.error("latestPoolDataFromDatabase is null - should never happen at this point! Cannot do encryption cleanup"); + return; + } + + /* + * Skip if this instance is outdated itself (because this instance could still + * create jobs with old ciphers) + */ + if (!Objects.equals(encryptionService.getLatestCipherPoolId(), latestPoolDataFromDatabase.getId())) { + LOG.debug("Encryption pool of this instance is outdated, cannot cleanup"); + return; + } + + startEncryptionCleanup(allPoolData, latestPoolDataFromDatabase); + + } + + private void startEncryptionCleanup(List allPoolData, ScheduleCipherPoolData latestPoolDataFromDatabase) { + LOG.debug("Encryption pool cleanup start"); + + List poolDataToRemove = calculatePoolDataToRemove(allPoolData, latestPoolDataFromDatabase); + if (poolDataToRemove.isEmpty()) { + LOG.debug("Found no pool data to remove"); + return; + } + + LOG.info("Found {} pool entries to remove - start deletetion process", poolDataToRemove.size()); + + for (ScheduleCipherPoolData poolData : poolDataToRemove) { + + LOG.info("Start delete of encryption pool entry: id='{}', algorithm='{}', pwdSourceType='{}', pwdSourceData='{}', created='{}', createdFrom='{}' ", + poolData.getId(), poolData.getAlgorithm(), poolData.getPasswordSourceType(), poolData.getPasswordSourceData(), poolData.getCreated(), + poolData.getCreatedFrom()); + + poolDataRepository.delete(poolData); + } + } + + private List calculatePoolDataToRemove(List allPoolData, + ScheduleCipherPoolData latestPoolDataFromDatabase) { + + List allUsedEncryptionPoolIds = jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs(); + + List poolDataToRemove = new ArrayList<>(); + + for (ScheduleCipherPoolData poolData : allPoolData) { + boolean mustBeKept = latestPoolDataFromDatabase.equals(poolData); + mustBeKept = mustBeKept || allUsedEncryptionPoolIds.contains(poolData.getId()); + + if (mustBeKept) { + continue; + } + poolDataToRemove.add(poolData); + } + return poolDataToRemove; + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolData.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolData.java new file mode 100644 index 0000000000..093ec0b175 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolData.java @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static jakarta.persistence.EnumType.*; + +import java.time.LocalDateTime; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +/** + * PersistentCipher pool data table for domain 'schedule' inside database. + * Contains information about how existing schedule job entries are encrypted. + * + * @author Albert Tregnaghi + * + */ +@Entity +@Table(name = ScheduleCipherPoolData.TABLE_NAME) +public class ScheduleCipherPoolData { + + /* +-----------------------------------------------------------------------+ */ + /* +............................ SQL ......................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String TABLE_NAME = "SCHEDULE_CIPHER_POOL_DATA"; + + public static final String COLUMN_ID = "POOL_ID"; + + public static final String COLUMN_ALGORITHM = "POOL_ALGORITHM"; + + public static final String COLUMN_PWD_SOURCE_TYPE = "POOL_PWD_SRC_TYPE"; + + public static final String COLUMN_PWD_SOURCE_DATA = "POOL_PWD_SRC_DATA"; + + public static final String COLUMN_TEST_TEXT = "POOL_TEST_TEXT"; + public static final String COLUMN_TEST_INITIAL_VECTOR = "POOL_TEST_INITIAL_VECTOR"; + public static final String COLUMN_TEST_ENCRYPTED = "POOL_TEST_ENCRYPTED"; + + public static final String COLUMN_CREATION_TIMESTAMP = "POOL_CREATION_TIMESTAMP"; + public static final String COLUMN_CREATED_FROM = "POOL_CREATED_FROM"; + + /* +-----------------------------------------------------------------------+ */ + /* +............................ JPQL .....................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String CLASS_NAME = ScheduleCipherPoolData.class.getSimpleName(); + + public static final String PROPERTY_ID = "id"; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = COLUMN_ID, unique = true, nullable = false) + Long id; + + @Enumerated(STRING) + @Column(name = COLUMN_ALGORITHM, nullable = false) + SecHubCipherAlgorithm algorithm; + + @Enumerated(STRING) + @Column(name = COLUMN_PWD_SOURCE_TYPE, nullable = false) + SecHubCipherPasswordSourceType secHubCipherPasswordSourceType; + + @Column(name = COLUMN_PWD_SOURCE_DATA, nullable = true) + String passwordSourceData; + + @Column(name = COLUMN_TEST_TEXT, nullable = false) + String testText; + + @Column(name = COLUMN_TEST_INITIAL_VECTOR, nullable = true) + byte[] testInitialVector; + + @Column(name = COLUMN_TEST_ENCRYPTED, nullable = false) + byte[] testEncrypted; + + @Column(name = COLUMN_CREATION_TIMESTAMP, nullable = false) // remark: we setup hibernate to use UTC settings - see + LocalDateTime created; + + @Column(name = COLUMN_CREATED_FROM, nullable = true) + String createdFrom; + + @Version + @Column(name = "VERSION") + Integer version; + + public Long getId() { + return id; + } + + public SecHubCipherAlgorithm getAlgorithm() { + return algorithm; + } + + public SecHubCipherPasswordSourceType getPasswordSourceType() { + return secHubCipherPasswordSourceType; + } + + public String getPasswordSourceData() { + return passwordSourceData; + } + + public String getTestText() { + return testText; + } + + public byte[] getTestInitialVector() { + return testInitialVector; + } + + public byte[] getTestEncrypted() { + return testEncrypted; + } + + public LocalDateTime getCreated() { + return created; + } + + public String getCreatedFrom() { + return createdFrom; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ScheduleCipherPoolData other = (ScheduleCipherPoolData) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataProvider.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataProvider.java new file mode 100644 index 0000000000..ced866b260 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataProvider.java @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; + +/** + * Provides cipher pool data + * + * @author Albert Tregnaghi + * + */ +@Component +public class ScheduleCipherPoolDataProvider { + + private static final Logger LOG = LoggerFactory.getLogger(ScheduleCipherPoolDataProvider.class); + + @Autowired + ScheduleCipherPoolDataRepository repository; + + /** + * Resolves all existing cipher pool data from database. If none is available, a + * fallback will be persisted additionally. + * + * @return list of cipher pool data, never empty or null + */ + public List ensurePoolDataAvailable() { + List allPoolDataEntries = repository.findAll(); + + if (allPoolDataEntries.isEmpty()) { + + ScheduleCipherPoolData fallbackEntry = createFallback(); + repository.save(fallbackEntry); + + LOG.warn( + "No pool data entry found - created and stored fallback with algorithm: {}. Remark: This may only happen inside tests! Real server have default initialized by SQL scripts!", + fallbackEntry.getAlgorithm()); + allPoolDataEntries = List.of(fallbackEntry); + } + + if (allPoolDataEntries.isEmpty()) { + throw new IllegalStateException("Found no pool data entry - should never happen"); + } + return allPoolDataEntries; + } + + private ScheduleCipherPoolData createFallback() { + ScheduleCipherPoolData fallbackEntry = new ScheduleCipherPoolData(); + fallbackEntry.id = Long.valueOf(0); + fallbackEntry.secHubCipherPasswordSourceType = SecHubCipherPasswordSourceType.NONE; + fallbackEntry.testText = "fallback"; + fallbackEntry.testEncrypted = "fallback".getBytes(); + fallbackEntry.algorithm = SecHubCipherAlgorithm.NONE; + fallbackEntry.created = LocalDateTime.now(); + fallbackEntry.createdFrom = null; + fallbackEntry.testInitialVector = null; + fallbackEntry.version = Integer.valueOf(0); + return fallbackEntry; + } + + /** + * Checks if given set of pool ids are exactly the same pool ids available + * inside database + * + * @param currentPoolIds + * @return true when same pool ids, false if there is + * any difference + * @throws IllegalArgumentException if current pool id set is null + */ + public boolean isContainingExactlyGivenPoolIds(Set currentPoolIds) { + if (currentPoolIds == null) { + throw new IllegalArgumentException("Current pool ids may not be null!"); + } + + Set poolIdsFromDatabase = repository.fetchAllCipherPoolIds(); + + int dbSize = poolIdsFromDatabase.size(); + int memorySize = currentPoolIds.size(); + + if (dbSize != memorySize) { + LOG.debug("Pool size differs. Memory: {}, Database: {}", memorySize, dbSize); + return false; + } + + boolean allPoolIdsContained = poolIdsFromDatabase.containsAll(currentPoolIds); + + LOG.trace("all pool ids contained: {}, found pool ids in db: {}", allPoolIdsContained, poolIdsFromDatabase); + + return allPoolIdsContained; + } + +} \ No newline at end of file diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataRepository.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataRepository.java new file mode 100644 index 0000000000..e509daa061 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataRepository.java @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.util.Set; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface ScheduleCipherPoolDataRepository extends JpaRepository { + + @Query("select " + ScheduleCipherPoolData.PROPERTY_ID + " from #{#entityName}") + public Set fetchAllCipherPoolIds(); +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataTransactionService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataTransactionService.java new file mode 100644 index 0000000000..3114b6c210 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataTransactionService.java @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminStartsEncryptionRotation; + +@Service +public class ScheduleCipherPoolDataTransactionService { + + @Autowired + ScheduleCipherPoolDataRepository repository; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + @UseCaseAdminStartsEncryptionRotation(@Step(number = 4, name = "Service call", description = "Creates new cipher pool entry in database in own transaction")) + public ScheduleCipherPoolData storeInOwnTransaction(ScheduleCipherPoolData poolData) throws ScheduleEncryptionException { + return repository.save(poolData); + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionException.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionException.java new file mode 100644 index 0000000000..b87a276494 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionException.java @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +public class ScheduleEncryptionException extends Exception { + + private static final long serialVersionUID = 1L; + + public ScheduleEncryptionException(String description) { + super(description); + } +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPojoFactory.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPojoFactory.java new file mode 100644 index 0000000000..26f2c8e16f --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPojoFactory.java @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionRotator; +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; + +/** + * This factory creates some "plain old java" objects and inject them into + * spring boot container. These objects are from libraries where we do not have + * spring annotations inside for automatic injection. + * + * @author Albert Tregnaghi + * + */ +@Component +public class ScheduleEncryptionPojoFactory { + + @Bean + PersistentCipherFactory createPersistentCipherFactory() { + return new PersistentCipherFactory(); + } + + @Bean + EncryptionSupport createEncryptionSupport() { + return new EncryptionSupport(); + } + + @Bean + EncryptionRotator createEncryptionRotator() { + return new EncryptionRotator(); + } +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPool.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPool.java new file mode 100644 index 0000000000..f8aada586b --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPool.java @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; + +/** + * This class represents the runtime container cache which provides + * {@link PersistentCipher} objects for dedicated pool identifier. + * + * It has another name than {@link ScheduleCipherPoolData} which represents the + * data source from database (but which is only used for creation of this pool). + * To differ between database entities and runtime container object, the names + * differ. + * + * @author Albert Tregnaghi + * + */ +public class ScheduleEncryptionPool { + + Map poolDataIdToPersistentCipherMap = new LinkedHashMap<>(); + + ScheduleEncryptionPool(Map map) { + if (map != null) { + poolDataIdToPersistentCipherMap.putAll(map); + } + } + + /** + * Resolves cipher for given pool id + * + * @param poolId + * @return cipher instance or null if not found inside pool + */ + public PersistentCipher getCipherForPoolId(Long poolId) { + return poolDataIdToPersistentCipherMap.get(poolId); + } + + public Set getAllPoolIds() { + return new HashSet<>(poolDataIdToPersistentCipherMap.keySet()); + } +} \ No newline at end of file diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolFactory.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolFactory.java new file mode 100644 index 0000000000..b92550107a --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolFactory.java @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubSecretKeyProviderFactory; + +@Component +public class ScheduleEncryptionPoolFactory { + + @Autowired + EncryptionSupport encryptionSupport; + + @Autowired + PersistentCipherFactory cipherFactory; + + @Autowired + SecHubSecretKeyProviderFactory secHubSecretKeyProviderFactory; + + /** + * Creates an encryption pool for the given list of scheduler cipher pool data. + * The factory will create the necessary persistent cipher instances and + * automatically check that the encryption is possible with the current setup. + * + * If it is not possible - e.g. a cipher is created but with the wrong password + * - the factory will throw an ScheduleEncryptionException + * + * @param allPoolDataEntries - may not be null + * @return + */ + public ScheduleEncryptionPool createEncryptionPool(List allPoolDataEntries) throws ScheduleEncryptionException { + if (allPoolDataEntries == null) { + throw new IllegalArgumentException("allPoolDataEntries may never be null!"); + } + + Map map = new LinkedHashMap<>(); + + for (ScheduleCipherPoolData poolData : allPoolDataEntries) { + + PersistentCipher cipher = createCipher(poolData.getAlgorithm().getType(), poolData.getPasswordSourceType(), poolData.getPasswordSourceData()); + + byte[] testEncrypted = poolData.getTestEncrypted(); + InitializationVector initialVector = new InitializationVector(poolData.getTestInitialVector()); + + String decrypted = encryptionSupport.decryptString(testEncrypted, cipher, initialVector); + + String expected = poolData.getTestText(); + + if (!expected.equals(decrypted)) { + /* + * We block server start here because a new server must always be able to handle + * the complete encryption pool. + */ + throw new ScheduleEncryptionException( + "The cipher pool entry with id: %d cannot be handled by the server, because origin test text: '%s' was not retrieved from encrypted test text data, but instead: '%s'" + .formatted(poolData.getId(), expected, decrypted)); + } + + /* Server is able to encrypt/decrypt data with given secret - register it */ + map.put(poolData.getId(), cipher); + + } + return new ScheduleEncryptionPool(map); + } + + private PersistentCipher createCipher(PersistentCipherType cipherType, SecHubCipherPasswordSourceType passwordSourceType, String passwordSourceData) { + + SecretKeyProvider secretKeyProvider = secHubSecretKeyProviderFactory.createSecretKeyProvider(cipherType, passwordSourceType, passwordSourceData); + + return cipherFactory.createCipher(secretKeyProvider, cipherType); + + } +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionResult.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionResult.java new file mode 100644 index 0000000000..2df5d71546 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionResult.java @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; + +public class ScheduleEncryptionResult { + private Long cipherPoolId; + private EncryptionResult encryptionResult; + + /** + * Creates encryption result + * + * @param cipherPoolId may never be null + * @param encryptionResult may never be null + * @throws IllegalArgumentException if one of the arguments is null + */ + public ScheduleEncryptionResult(Long cipherPoolId, EncryptionResult encryptionResult) { + if (cipherPoolId == null) { + throw new IllegalArgumentException("cipher pool id may never be null!"); + } + if (encryptionResult == null) { + throw new IllegalArgumentException("encryptionResult may never be null!"); + } + this.cipherPoolId = cipherPoolId; + this.encryptionResult = encryptionResult; + } + + /** + * @return cipher pool id used for encryption, never null + */ + public Long getCipherPoolId() { + return cipherPoolId; + } + + /** + * @return encrypted data or null + */ + public byte[] getEncryptedData() { + return encryptionResult.getEncryptedData(); + } + + /** + * @return initial vector, never null + */ + public InitializationVector getInitialVector() { + return encryptionResult.getInitialVector(); + } + +} \ No newline at end of file diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionRotationService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionRotationService.java new file mode 100644 index 0000000000..3377472f16 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionRotationService.java @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminStartsEncryptionRotation; + +@Service +public class ScheduleEncryptionRotationService { + + private static final Logger LOG = LoggerFactory.getLogger(ScheduleEncryptionRotationService.class); + + @Autowired + ScheduleCipherPoolDataTransactionService transactionService; + + @Autowired + ScheduleEncryptionService encryptionService; + + @UseCaseAdminStartsEncryptionRotation(@Step(number = 3, name = "Service call", description = "Forces new cipher pool entry creation and triggers encryption service pool refresh")) + public void startEncryptionRotation(SecHubEncryptionData data, String executedBy) { + /* first create new cipher pool entry */ + try { + + LOG.info("start rotation encryption"); + + String testText = UUID.randomUUID().toString(); + + ScheduleCipherPoolData poolData = encryptionService.createInitialCipherPoolData(data, testText); + poolData.createdFrom = executedBy; + + ScheduleCipherPoolData newCreatedEntry = transactionService.storeInOwnTransaction(poolData); + + LOG.info("Created new cipher pool entry with id: {}, algorithm: {}, creation timestamp: {}, created from: {} ", newCreatedEntry.id, + newCreatedEntry.algorithm, newCreatedEntry.created, newCreatedEntry.createdFrom); + } catch (ScheduleEncryptionException e) { + LOG.error("Was not able to create new cipher pool entry!", e); + } + + LOG.info("Trigger refresh of encryption pool in this instance"); + encryptionService.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionService.java new file mode 100644 index 0000000000..d95aae0083 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionService.java @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.EncryptionRotationSetup; +import com.mercedesbenz.sechub.commons.encryption.EncryptionRotator; +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; +import com.mercedesbenz.sechub.domain.schedule.ScheduleShutdownService; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubSecretKeyProviderFactory; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsSendingAsyncMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminStartsEncryptionRotation; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseScheduleEncryptionPoolRefresh; + +@Service +public class ScheduleEncryptionService { + + private static final Logger LOG = LoggerFactory.getLogger(ScheduleEncryptionService.class); + + @Autowired + ScheduleCipherPoolDataProvider poolDataProvider; + + @Autowired + EncryptionSupport encryptionSupport; + + @Autowired + ScheduleEncryptionPoolFactory scheduleEncryptionPoolFactory; + + @Autowired + ScheduleLatestCipherPoolDataCalculator latestCipherPoolDataCalculator; + + @Autowired + EncryptionRotator rotator; + + @Autowired + PersistentCipherFactory cipherFactory; + + @Autowired + SecHubSecretKeyProviderFactory secretKeyProviderFactory; + + @Autowired + SecHubOutdatedEncryptionPoolSupport outdatedEncryptionPoolSupport; + + @Autowired + ScheduleShutdownService shutdownService; + + @Autowired + @Lazy + DomainMessageService domainMessageService; + + Long latestCipherPoolId; + + ScheduleEncryptionPool scheduleEncryptionPool; + + @EventListener(ApplicationStartedEvent.class) + @UseCaseScheduleEncryptionPoolRefresh(@Step(number = 1, next = 3, name = "Init encryption pool", description = "Encryption pool is created on startup")) + void applicationStarted() throws ScheduleEncryptionException { + // If a new started server is not able to handle all ciphers from cipher pool it + // will just + // not start (ensures old and new jobs can always be handled) + initNewEncryptionPoolOrFail(); + } + + /** + * @return latest cipher pool id supported by this service + */ + public Long getLatestCipherPoolId() { + return latestCipherPoolId; + } + + /** + * Tries to refresh encryption pool and latest id. Same like application startup + * - but if it is not possible to create a new encryption pool (e.g. new + * encryption is currently not supported by this server instance) just a warning + * is logged and the old encryption pool is still in use. + */ + @UseCaseScheduleEncryptionPoolRefresh(@Step(number = 2, next = 3, name = "Refresh encryption pool", description = "Encryption pool is refreshed (if necessary)")) + @UseCaseAdminStartsEncryptionRotation(@Step(number = 5, name = "Refresh encryption pool", description = "Encryption pool is refreshed (necessary because pool changed before this method call)")) + public void refreshEncryptionPoolAndLatestPoolIdIfNecessary() { + + if (isStillSameEncryptionPool()) { + LOG.trace("Encryption pool has not changed. No update necessary"); + return; + } + + LOG.info("Encryption pool has changed, start encryption pool recreation."); + + try { + initNewEncryptionPoolOrFail(); + LOG.info("Encryption pool has been recreated sucessfully."); + + } catch (Exception e) { + LOG.warn("Was not able to refresh encryption pool. Reason: {}", e.getMessage()); + + if (outdatedEncryptionPoolSupport.isOutdatedEncryptionStillAllowedOnThisClusterMember()) { + LOG.info("Failing encrytpion pool initialization is still accepted, will use old existing encryption pool."); + } else { + LOG.info("Old (outdated) encryption pool no longer accepted, will trigger shutdown"); + shutdownService.shutdownApplication(); + } + + } + + } + + private boolean isStillSameEncryptionPool() { + return poolDataProvider.isContainingExactlyGivenPoolIds(scheduleEncryptionPool.getAllPoolIds()); + } + + @IsSendingAsyncMessage(MessageID.SCHEDULE_ENCRYPTION_POOL_INITIALIZED) + private void initNewEncryptionPoolOrFail() throws ScheduleEncryptionException { + List allPoolDataEntries = poolDataProvider.ensurePoolDataAvailable(); + + scheduleEncryptionPool = scheduleEncryptionPoolFactory.createEncryptionPool(allPoolDataEntries); + + latestCipherPoolId = latestCipherPoolDataCalculator.calculateLatestPoolId(allPoolDataEntries); + if (latestCipherPoolId == null) { + throw new IllegalStateException("Was not able to determine latest cipher pool data!"); + } + + /* sanity check */ + if (scheduleEncryptionPool.getCipherForPoolId(latestCipherPoolId) == null) { + throw new IllegalStateException("Encryption pool has no entry for latest cipher pool id: %d".formatted(latestCipherPoolId)); + } + + LOG.info("Encryption pool created ({} pool entries)", allPoolDataEntries.size()); + + /* send message about new pool creation */ + DomainMessage message = new DomainMessage(MessageID.SCHEDULE_ENCRYPTION_POOL_INITIALIZED); + domainMessageService.sendAsynchron(message); + } + + public ScheduleEncryptionResult encryptWithLatestCipher(String string) { + return new ScheduleEncryptionResult(latestCipherPoolId, enryptToByteArray(string, latestCipherPoolId)); + } + + public String decryptToString(byte[] encrypted, Long encryptionPoolId, InitializationVector initialVector) { + PersistentCipher cipher = scheduleEncryptionPool.getCipherForPoolId(encryptionPoolId); + if (cipher == null) { + throw new IllegalStateException("There was no registered cipher entry for pool id: %d".formatted(encryptionPoolId)); + } + return encryptionSupport.decryptString(encrypted, cipher, initialVector); + } + + private EncryptionResult enryptToByteArray(String string, Long encryptionPoolId) { + PersistentCipher cipher = scheduleEncryptionPool.getCipherForPoolId(encryptionPoolId); + if (cipher == null) { + throw new IllegalStateException("There was no registered cipher entry for pool id: %d".formatted(encryptionPoolId)); + } + return encryptionSupport.encryptString(string, cipher); + } + + public ScheduleEncryptionResult rotateEncryption(byte[] data, Long oldCipherPoolId, InitializationVector oldInitialVector) + throws ScheduleEncryptionException { + if (latestCipherPoolId == null) { + throw new IllegalStateException("latest cipher pool id is null!"); + } + long newCipherPoolId = latestCipherPoolId; + + PersistentCipher oldCipher = scheduleEncryptionPool.getCipherForPoolId(oldCipherPoolId); + PersistentCipher newCipher = scheduleEncryptionPool.getCipherForPoolId(newCipherPoolId); + + if (oldCipher == null) { + throw new ScheduleEncryptionException("Old cipher not available: " + oldCipherPoolId); + } + if (newCipher == null) { + throw new ScheduleEncryptionException("New cipher not available: " + newCipherPoolId); + } + + InitializationVector newInitialVector = newCipher.createNewInitializationVector(); + + EncryptionRotationSetup rotateSetup = EncryptionRotationSetup.builder().newCipher(newCipher).oldCipher(oldCipher).oldInitialVector(oldInitialVector) + .newInitialVector(newInitialVector).build(); + + byte[] newEncrypted = rotator.rotate(data, rotateSetup); + EncryptionResult res = new EncryptionResult(newEncrypted, newInitialVector); + + return new ScheduleEncryptionResult(newCipherPoolId, res); + } + + /** + * Creates new initial cipher pool data entity. Will automatically check if + * encryption process can be done with this instance and given test text. Pool + * data will contain all relevant data, except information about user which was + * responsible for the new pool data. + * + * @param data encryption data + * @param testText text to be used for initial test + * @return initial pool data ready to be stored, but without setting the creator + * @throws ScheduleEncryptionException if encryption process has any problems + */ + public ScheduleCipherPoolData createInitialCipherPoolData(SecHubEncryptionData data, String testText) throws ScheduleEncryptionException { + + ScheduleCipherPoolData poolData = new ScheduleCipherPoolData(); + poolData.algorithm = data.getAlgorithm(); + poolData.created = LocalDateTime.now(); + poolData.passwordSourceData = data.getPasswordSourceData(); + poolData.secHubCipherPasswordSourceType = data.getPasswordSourceType(); + + poolData.testText = testText; + + PersistentCipherType cipherType = poolData.getAlgorithm().getType(); + SecretKeyProvider secretKey = secretKeyProviderFactory.createSecretKeyProvider(cipherType, poolData.getPasswordSourceType(), + poolData.getPasswordSourceData()); + PersistentCipher tempCipher = cipherFactory.createCipher(secretKey, cipherType); + + EncryptionResult result = encryptionSupport.encryptString(poolData.testText, tempCipher); + poolData.testInitialVector = result.getInitialVector().getInitializationBytes(); + poolData.testEncrypted = result.getEncryptedData(); + + // sanity check + String decrypted = encryptionSupport.decryptString(poolData.testEncrypted, tempCipher, new InitializationVector(poolData.getTestInitialVector())); + if (decrypted == null) { + throw new ScheduleEncryptionException("Was not able to instantiate new cipher pool data, because decrypted value is null!"); + } + if (!decrypted.equals(poolData.testText)) { + throw new ScheduleEncryptionException("Was not able to instantiate new cipher pool data, because decrypted value is not origin test text!"); + } + return poolData; + + } + + public Set getCurrentEncryptionPoolIds() { + return scheduleEncryptionPool.getAllPoolIds(); + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionStatusService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionStatusService.java new file mode 100644 index 0000000000..1ce4f1fde6 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionStatusService.java @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.model.job.ExecutionState; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageSynchronousResult; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsRecevingSyncMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsSendingSyncMessageAnswer; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; +import com.mercedesbenz.sechub.sharedkernel.messaging.SynchronMessageHandler; + +@Service +public class ScheduleEncryptionStatusService implements SynchronMessageHandler { + + private static final Logger LOG = LoggerFactory.getLogger(ScheduleEncryptionStatusService.class); + + @Autowired + ScheduleCipherPoolDataRepository poolDataRepository; + + @Autowired + SecHubJobRepository jobRepository; + + @Override + public DomainMessageSynchronousResult receiveSynchronMessage(DomainMessage request) { + LOG.debug("received synchronnous domain request: {}", request); + + MessageID messageId = request.getMessageId(); + + switch (messageId) { + case GET_ENCRYPTION_STATUS_SCHEDULE_DOMAIN: + return handleEncryptionStatusRequest(); + default: + throw new IllegalStateException("unhandled message id:" + messageId); + } + + } + + @IsRecevingSyncMessage(MessageID.GET_ENCRYPTION_STATUS_SCHEDULE_DOMAIN) + @IsSendingSyncMessageAnswer(value = MessageID.RESULT_ENCRYPTION_STATUS_SCHEDULE_DOMAIN, answeringTo = MessageID.GET_ENCRYPTION_STATUS_SCHEDULE_DOMAIN, branchName = "success") + private DomainMessageSynchronousResult handleEncryptionStatusRequest() { + + SecHubDomainEncryptionStatus status = createEncryptionStatus(); + + DomainMessageSynchronousResult result = new DomainMessageSynchronousResult(MessageID.RESULT_ENCRYPTION_STATUS_SCHEDULE_DOMAIN); + result.set(MessageDataKeys.SECHUB_DOMAIN_ENCRYPTION_STATUS, status); + return result; + } + + public SecHubDomainEncryptionStatus createEncryptionStatus() { + SecHubDomainEncryptionStatus status = new SecHubDomainEncryptionStatus(); + status.setName("schedule"); + + List all = poolDataRepository.findAll(); + + for (ScheduleCipherPoolData cipherPoolData : all) { + Long cipherPoolid = cipherPoolData.getId(); + + // initialize + SecHubDomainEncryptionData data = new SecHubDomainEncryptionData(); + data.setId(String.valueOf(cipherPoolid)); + data.setAlgorithm(cipherPoolData.getAlgorithm()); + data.getPasswordSource().setType(cipherPoolData.getPasswordSourceType()); + data.getPasswordSource().setData(cipherPoolData.getPasswordSourceData()); + data.setCreated(cipherPoolData.getCreated()); + data.setCreatedFrom(cipherPoolData.getCreatedFrom()); + + // add usage + for (ExecutionState state : ExecutionState.values()) { + long count = jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(state, cipherPoolid); + data.getUsage().put("job.state." + state.toString().toLowerCase(), count); + } + status.getData().add(data); + + } + + return status; + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleLatestCipherPoolDataCalculator.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleLatestCipherPoolDataCalculator.java new file mode 100644 index 0000000000..af27008e93 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleLatestCipherPoolDataCalculator.java @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.util.List; + +import org.springframework.stereotype.Component; + +@Component +public class ScheduleLatestCipherPoolDataCalculator { + + /** + * Calculates latest id from given list of data entries + * + * @param entries a list of pool data elements + * @return latest id of list, or null of entry list was empty or + * null + */ + public Long calculateLatestPoolId(List entries) { + ScheduleCipherPoolData latestCipherPoolData = calculateLatestPoolData(entries); + if (latestCipherPoolData == null) { + return null; + } + return latestCipherPoolData.getId(); + } + + /** + * Calculates latest entry from given list of data entries + * + * @param entries a list of pool data elements + * @return latest entry of list, or null of entry list was empty or + * null + */ + public ScheduleCipherPoolData calculateLatestPoolData(List entries) { + if (entries == null || entries.isEmpty()) { + return null; + } + + ScheduleCipherPoolData latestCipherPoolData = null; + for (ScheduleCipherPoolData poolData : entries) { + + /* calculate latest */ + if (latestCipherPoolData == null) { + latestCipherPoolData = poolData; + } else { + if (latestCipherPoolData.getCreated().isBefore(poolData.getCreated())) { + latestCipherPoolData = poolData; + } + } + } + return latestCipherPoolData; + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleRefreshEncryptionServiceSetupTriggerService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleRefreshEncryptionServiceSetupTriggerService.java new file mode 100644 index 0000000000..b56064b425 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleRefreshEncryptionServiceSetupTriggerService.java @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseScheduleEncryptionPoolRefresh; + +/** + * This service is responsible to trigger periodically refresh checks on the + * encryption service to update encryption pool and latest pool id when + * necessary. + * + * The reason for the periodic check is that we do not want to check for latest + * cipher etc. on every job creation or every encryption rotation - we separated + * the pool refresh and the encryption/re-encryption (which will always use the + * encryption pool and its information). + * + * @author Albert Tregnaghi + * + */ +@Service +public class ScheduleRefreshEncryptionServiceSetupTriggerService { + + private static final int DEFAULT_INITIAL_DELAY_MILLIS = 5 * 1000; // 5 seconds delay + private static final int DEFAULT_FIXED_DELAY_MILLIS = 5 * 60 * 1000; // 5 minutes + + static final String SPRING_VALUE_INITIAL_DELAY_MILLISECONDS = "${sechub.schedule.encryption.refresh.initialdelay:" + DEFAULT_INITIAL_DELAY_MILLIS + "}"; + static final String SPRING_VALUE_FIXED_DELAY_MILLISECONDS = "${sechub.schedule.encryption.refresh.delay:" + DEFAULT_FIXED_DELAY_MILLIS + "}"; + + private static final String DESCRIPTION = "Scheduler instance will check if encryption pool is in sync with the database definitions. If not, the instance will try to create new encryption pool object and provide the new setup."; + + @Autowired + ScheduleEncryptionService encryptionService; + + @MustBeDocumented("Defines the initial and also the fixed delay for the refresh interval. These values are also used for calculation of remaining run time of outdated encrytion pools (when refresh fails)") + @Scheduled(initialDelayString = SPRING_VALUE_INITIAL_DELAY_MILLISECONDS, fixedDelayString = SPRING_VALUE_FIXED_DELAY_MILLISECONDS) + @UseCaseScheduleEncryptionPoolRefresh(@Step(number = 1, name = "Encryption pool data refresh trigger", description = DESCRIPTION)) + public void triggerEncryptionSetupRefresh() { + encryptionService.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + } +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/SecHubOutdatedEncryptionPoolSupport.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/SecHubOutdatedEncryptionPoolSupport.java new file mode 100644 index 0000000000..aeefef4296 --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/encryption/SecHubOutdatedEncryptionPoolSupport.java @@ -0,0 +1,161 @@ +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; +import com.mercedesbenz.sechub.sharedkernel.SystemTimeProvider; + +@Component +public class SecHubOutdatedEncryptionPoolSupport { + + /* + * This is just an additional time buffer when calculating the maximum refresh + * interval time + */ + private static final int MAX_REFRESH_INTERVAL_ADDITIONAL_TIME_BUFFER_MILLISECONDS = 500; + private static final int DEFAULT_ACCEPT_OUTDATED_POOL_IN_MILLISECONDS = 30 * 60 * 1000; // 30 minutes + + @MustBeDocumented("The maximum amount of milliseconds an outdated encryption pool is still accepted in refresh phase") + @Value("${sechub.schedule.encryption.refresh.accept-outdated.milliseconds:" + DEFAULT_ACCEPT_OUTDATED_POOL_IN_MILLISECONDS + "}") + long acceptOutdatedEncryptionPoolInMilliseconds; + + // documented in ScheduleRefreshEncryptionServiceSetupTriggerService + @Value(ScheduleRefreshEncryptionServiceSetupTriggerService.SPRING_VALUE_FIXED_DELAY_MILLISECONDS) + long encryptionRefreshFixedDelayInMilliseconds; + + // documented in ScheduleRefreshEncryptionServiceSetupTriggerService + @Value(ScheduleRefreshEncryptionServiceSetupTriggerService.SPRING_VALUE_INITIAL_DELAY_MILLISECONDS) + long encryptionRefreshInitialDelayInMilliseconds; + + @Autowired + SystemTimeProvider systemtimeProvider; + + @Autowired + ScheduleCipherPoolDataProvider poolDataProvider; + + @Autowired + ScheduleLatestCipherPoolDataCalculator latestCipherPoolDataCalculator; + + /** + * As long as an outdated encryption pool still needs to run (e.g. while a + * rolling update/ deployment is done with K8s and the old server is still up + * and running and does create jobs...). + * + * The method will check if the maximum allowed time for an outdated encryption + * pool has been reached by following algorithm: + *
      + *
    • the creation time stamp of latest cipher pool data entry from database is + * fetched
    • + *
    • the difference from current time and the creation time is calculated
    • + *
    • if the difference is bigger than the allowed maximum value this method + * returns false, otherwise true
    • + *
    + * + * @return true as long as still acceptable, otherwise + * false + */ + public boolean isOutdatedEncryptionStillAllowedOnThisClusterMember() { + Long latestPoolDataWasCreatedMillisecondsBefore = calculateMillisecondsLatestPoolDataHasBeenCreated(); + if (latestPoolDataWasCreatedMillisecondsBefore == null) { + /* no pool defined, means cannot determine - return false */ + return false; + } + return acceptOutdatedEncryptionPoolInMilliseconds > latestPoolDataWasCreatedMillisecondsBefore; + } + + /** + * This method is used to avoid the risk of race conditions when encryption pool + * entries are still in use but wanted to be removed. + *

    Example-Situation:

    + * + *
    +     * SecHub-Cluster member A: - SECRET_1 - SECRET_2 (new encryption pool entry PA, poolId = 2)
    +     *
    +     * SecHub-Cluster member B: - SECRET_1 (old encryption pool entry PB, poolId = 1)
    +     * 
    + * + * SecHub B creates now a new job which is currently not stored in database but + * with old encryption pool PB (poolId=1). In the mean time cluster member A + * does not find any jobs which using pool id 1 and could delete now the + * encryption pool entity with id 1...
    + *
    + * After this the member B would write the encrypted configuration with poolId=1 + * to database, because the encryption pool is still in memory and its latest + * entry is pool id 1 -> after this member 2 is also new started with only + * SECRET_2 settings.. + * + * --> Now we have a bad situation: we would have a created job encrypted with + * SECRET_1, but no possibility to handle the created job by SecHub any more. + * + * + *

    Problem

    + * + * SecHub does not use a full blown event bus (like KAFKA) but a simple event + * listener approach which works only inside one JVM, we have no possibility to + * send cluster wide events to check there are no longer outdated encryption + * pools used. + * + *

    Solution

    To handle the problem without a full blown event bus, we + * ensure that outdated encryption pools can remain running only a dedicated + * time. The time an outdated encryption pool is accepted is calculated by + * {@link #isOutdatedEncryptionStillAllowedOnThisClusterMember()}. + * + * Means we ask here the question: "Was the maximum outdate time exceeded at the + * last refresh interval?" + * + *
    +     *      Created                     Cluster wrong EP          Cluster wrong EP
    +     *        New      Refresh              possible:  Refresh           possible:
    +     *        Pool Id   Trigger                        Trigger
    +     * +-----------------+--------------------Y-----------+--------------------N-------------------
    +     *         |        Outdated                        Outdated               |
    +     *         |         |                                |                    |
    +     *         |         |----------------------------x   not                  |
    +     *         |            still allowed on cluster      allowed              |
    +     *         |            member                    (max time diff reached)  |
    +     *         |<------------------------------------------------------------->|
    +     *         |  (time from created to now)                                  (now)
    +     *         |
    +     *         |<------------------------------------------>X<-----------------|
    +     *         | (time from created to last refresh trigger)| (now-refresh trigger time)
    +     *                                                      |
    +     *                                                      |
    +     *                                                  timeOnLastRefreshTigger
    +     * 
    + * + * + * @return true when outdated encryption pool is possible in + * cluster, otherwise false + */ + public boolean isOutdatedEncryptionPoolPossibleInCluster() { + Long latestPoolDataWasCreatedMillisecondsBefore = calculateMillisecondsLatestPoolDataHasBeenCreated(); + if (latestPoolDataWasCreatedMillisecondsBefore == null) { + /* no pool defined, means outdated is not possible */ + return false; + } + + long maxRefreshIntervalTimeMillseconds = encryptionRefreshInitialDelayInMilliseconds + encryptionRefreshFixedDelayInMilliseconds + + MAX_REFRESH_INTERVAL_ADDITIONAL_TIME_BUFFER_MILLISECONDS; + + long maxMillisecondsDifferenceToLastRefreshTigger = Math.abs(latestPoolDataWasCreatedMillisecondsBefore - maxRefreshIntervalTimeMillseconds); + + return acceptOutdatedEncryptionPoolInMilliseconds > maxMillisecondsDifferenceToLastRefreshTigger; + } + + private Long calculateMillisecondsLatestPoolDataHasBeenCreated() { + List allAvailablePoolData = poolDataProvider.ensurePoolDataAvailable(); + ScheduleCipherPoolData latest = latestCipherPoolDataCalculator.calculateLatestPoolData(allAvailablePoolData); + if (latest == null) { + return null; + } + LocalDateTime latestCreationTimeStamp = latest.getCreated(); + + return Duration.between(latestCreationTimeStamp, systemtimeProvider.getNow()).toMillis(); + } +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/ScheduleSecHubJob.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/ScheduleSecHubJob.java index 049b57deed..3d908c860a 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/ScheduleSecHubJob.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/ScheduleSecHubJob.java @@ -48,7 +48,11 @@ public class ScheduleSecHubJob { public static final String COLUMN_STARTED = "STARTED"; public static final String COLUMN_ENDED = "ENDED"; public static final String COLUMN_STATE = "STATE"; - public static final String COLUMN_CONFIGURATION = "CONFIGURATION"; + + public static final String COLUMN_ENCRYPTED_CONFIGURATION = "ENCRYPTED_CONFIGURATION"; + public static final String COLUMN_ENCRYPT_INITIAL_VECTOR = "ENCRYPT_INITIAL_VECTOR"; + public static final String COLUMN_ENCRYPT_POOL_DATA_ID = "ENCRYPT_POOL_DATA_ID"; + public static final String COLUMN_TRAFFIC_LIGHT = "TRAFFIC_LIGHT"; public static final String COLUMN_MODULE_GROUP = "MODULE_GROUP"; @@ -74,6 +78,7 @@ public class ScheduleSecHubJob { public static final String PROPERTY_MESSAGES = "jsonMessages"; public static final String PROPERTY_MODULE_GROUP = "moduleGroup"; public static final String PROPERTY_DATA = "data"; + public static final String PROPERTY_ENCRYPTION_POOL_ID = "encryptionCipherPoolId"; public static final String QUERY_DELETE_JOB_OLDER_THAN = "DELETE FROM ScheduleSecHubJob j WHERE j." + PROPERTY_CREATED + " <:cleanTimeStamp"; @@ -98,8 +103,14 @@ public class ScheduleSecHubJob { @Column(name = COLUMN_ENDED) // remark: we setup hibernate to use UTC settings - see application.properties LocalDateTime ended; - @Column(name = COLUMN_CONFIGURATION) - String jsonConfiguration; + @Column(name = COLUMN_ENCRYPTED_CONFIGURATION) + byte[] encryptedConfiguration; + + @Column(name = COLUMN_ENCRYPT_INITIAL_VECTOR) + byte[] encryptionInitialVectorData; + + @Column(name = COLUMN_ENCRYPT_POOL_DATA_ID) + Long encryptionCipherPoolId; @Enumerated(STRING) @Column(name = COLUMN_STATE, nullable = false) @@ -179,10 +190,6 @@ public LocalDateTime getCreated() { return created; } - public String getJsonConfiguration() { - return jsonConfiguration; - } - public ExecutionState getExecutionState() { return executionState; } @@ -211,6 +218,30 @@ public ModuleGroup getModuleGroup() { return moduleGroup; } + public byte[] getEncryptedConfiguration() { + return encryptedConfiguration; + } + + public void setEncryptedConfiguration(byte[] encryptedConfiguration) { + this.encryptedConfiguration = encryptedConfiguration; + } + + public byte[] getEncryptionInitialVectorData() { + return encryptionInitialVectorData; + } + + public void setEncryptionInitialVectorData(byte[] encryptionInitialVectorData) { + this.encryptionInitialVectorData = encryptionInitialVectorData; + } + + public void setEncryptionCipherPoolId(Long encryptionPoolDataId) { + this.encryptionCipherPoolId = encryptionPoolDataId; + } + + public Long getEncryptionCipherPoolId() { + return encryptionCipherPoolId; + } + @Override public int hashCode() { final int prime = 31; diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/ScheduleSecHubJobEncryptionUpdateService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/ScheduleSecHubJobEncryptionUpdateService.java new file mode 100644 index 0000000000..afb68bf64d --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/ScheduleSecHubJobEncryptionUpdateService.java @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.job; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionException; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionResult; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseAdminStartsEncryptionRotation; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseScheduleEncryptionPoolRefresh; +import com.mercedesbenz.sechub.sharedkernel.usecases.encryption.UseCaseScheduleRotateDataEncryption; + +@Service +public class ScheduleSecHubJobEncryptionUpdateService { + + private static final Logger LOG = LoggerFactory.getLogger(ScheduleSecHubJobEncryptionUpdateService.class); + + @Autowired + ScheduleEncryptionService encryptionService; + + @Autowired + SecHubJobTransactionService jobTransactionService; + + @Value("${sechub.schedule.job.encryption.update.blocksize:50}") // 50 per default + int updateBlockSize; + + @Value("${sechub.schedule.job.encryption.update.statuslog.milliseconds:10000}") // every 10 seconds per default + long milliSecondsForNextStatusLog; + + @UseCaseAdminStartsEncryptionRotation(@Step(number = 6, name = "Update encrypted data", description = "Encrypted data is updated (a direct pool refresh was triggered by admin action)")) + @UseCaseScheduleEncryptionPoolRefresh(@Step(number = 3, name = "Update encrypted data", description = "Encrypted data is updated (all other cluster members)")) + @UseCaseScheduleRotateDataEncryption(@Step(number = 1, name = "Update encrypted data", description = "Final update of encrypted job data. Will update all SecHub jobs having a pool id which is lower than latest from encryption pool")) + public void updateEncryptedDataIfNecessary() { + LOG.debug("Start update of encrypted data"); + + try { + + long lastStatusLogTime = 0; + + do { + + Long latestPoolid = encryptionService.getLatestCipherPoolId(); + + long statusLogTimeDifferenceInMilliseconds = System.currentTimeMillis() - lastStatusLogTime; + + if (statusLogTimeDifferenceInMilliseconds > milliSecondsForNextStatusLog) { + long count = jobTransactionService.countCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(latestPoolid); + LOG.info("Found {} jobs which are encrypted with a cipher pool entry lower than: {}", count, latestPoolid); + + lastStatusLogTime = System.currentTimeMillis(); + } + + /* 1. fetch next job (s) which shall be updated */ + List list = null; + try { + list = jobTransactionService.nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(latestPoolid, updateBlockSize); + if (list.isEmpty()) { + LOG.debug("No jobs found which must be updated."); + break; + } + + /* 2. for every entry, rotate and save the job afterwards */ + int updatedJobs = 0; + for (ScheduleSecHubJob job : list) { + if (rotateJobDataEncryptionAndStoreFailSafe(job)) { + updatedJobs++; + } + } + LOG.debug("Tried re-encryption of {} jobs, {} were succesful. Block size was: {}", list.size(), updatedJobs, updateBlockSize); + } catch (ObjectOptimisticLockingFailureException e) { + LOG.info("Optmistic lock problem detected - will just retry"); + } + + } while (true); + + } catch (ScheduleEncryptionException e) { + LOG.error("Was not able toupdate encrypted data because of encrpytion problem - will stop complete update. Check cipher setup!", e); + } + LOG.debug("Encrypted data update done"); + + } + + private boolean rotateJobDataEncryptionAndStoreFailSafe(ScheduleSecHubJob job) throws ScheduleEncryptionException { + LOG.trace("rotate job with uuid: {}", job.getUUID()); + + try { + ScheduleEncryptionResult result = encryptionService.rotateEncryption(job.getEncryptedConfiguration(), job.getEncryptionCipherPoolId(), + new InitializationVector(job.getEncryptionInitialVectorData())); + + job.setEncryptedConfiguration(result.getEncryptedData()); + job.setEncryptionCipherPoolId(result.getCipherPoolId()); + job.setEncryptionInitialVectorData(result.getInitialVector().getInitializationBytes()); + + jobTransactionService.saveInOwnTransaction(job); + + return true; + + } catch (OptimisticLockingFailureException lockException) { + LOG.debug("Job encryption for job: {} was not possible because row updated in mean time - either no longer necessary or will be done later again.", + job.getUUID()); + return false; + } + + } + +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubConfigurationModelAccessService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubConfigurationModelAccessService.java new file mode 100644 index 0000000000..7ccc2b72ee --- /dev/null +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubConfigurationModelAccessService.java @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.job; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; + +@Service +public class SecHubConfigurationModelAccessService { + + private static final Logger LOG = LoggerFactory.getLogger(SecHubConfigurationModelAccessService.class); + + @Autowired + @Lazy + ScheduleEncryptionService encryptionService; + + /** + * Resolves SecHub configuration model + * + * @param job + * @return model or null if job contains no encrypted configuration + */ + public SecHubConfigurationModel resolveUnencryptedConfiguration(ScheduleSecHubJob job) { + String json = resolveUnencryptedConfigurationasJson(job); + + SecHubConfigurationModel configuration = JSONConverter.get().fromJSON(SecHubConfigurationModel.class, json); + + return configuration; + } + + /** + * Resolves SecHub configuration model + * + * @param job + * @return model or null if job contains no encrypted configuration + */ + public String resolveUnencryptedConfigurationasJson(ScheduleSecHubJob job) { + if (job == null) { + throw new IllegalArgumentException("job parameter may not be null!"); + } + byte[] encryptedConfiguration = job.getEncryptedConfiguration(); + if (encryptedConfiguration == null) { + LOG.debug("No encrypted sechub configuration found for job: {}!", job.getUUID()); + return null; + } + Long encryptionCipherPoolId = job.getEncryptionCipherPoolId(); + InitializationVector initialVector = new InitializationVector(job.getEncryptionInitialVectorData()); + + String json = encryptionService.decryptToString(job.getEncryptedConfiguration(), encryptionCipherPoolId, initialVector); + + return json; + } +} diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java index b04e116171..a5be4f1a64 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactory.java @@ -16,6 +16,8 @@ import com.mercedesbenz.sechub.commons.model.ModuleGroup; import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelSupport; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionResult; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.sharedkernel.UserContextService; import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; @@ -30,6 +32,9 @@ public class SecHubJobFactory { @Autowired SecHubConfigurationModelSupport modelSupport; + @Autowired + ScheduleEncryptionService encryptionService; + private static final Logger LOG = LoggerFactory.getLogger(SecHubJobFactory.class); /** @@ -45,10 +50,16 @@ public ScheduleSecHubJob createJob(@Valid SecHubConfiguration configuration) { throw new IllegalStateException("No user logged in - illegal access!"); } + ScheduleEncryptionResult scheduleEncryptionResult = encryptionService.encryptWithLatestCipher(configuration.toJSON()); + ScheduleSecHubJob job = new ScheduleSecHubJob(); try { job.projectId = configuration.getProjectId(); - job.jsonConfiguration = configuration.toJSON(); + + job.encryptedConfiguration = scheduleEncryptionResult.getEncryptedData(); + job.encryptionInitialVectorData = scheduleEncryptionResult.getInitialVector().getInitializationBytes(); + job.encryptionCipherPoolId = scheduleEncryptionResult.getCipherPoolId(); + job.owner = userId; job.created = LocalDateTime.now(); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserService.java index c4ad924ea9..51106765d7 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserService.java @@ -17,9 +17,9 @@ import org.springframework.stereotype.Service; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationMetaData; +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelValidationResult; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelValidator; -import com.mercedesbenz.sechub.commons.model.SecHubScanConfiguration; import com.mercedesbenz.sechub.domain.schedule.ScheduleAssertService; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.sharedkernel.Step; @@ -55,6 +55,9 @@ public class SecHubJobInfoForUserService { @Autowired SecHubConfigurationModelValidator modelValidator; + @Autowired + SecHubConfigurationModelAccessService configurationModelAccess; + @Value("${sechub.project.joblist.size.max:" + DEFAULT_MAXIMUM_LIMIT + "}") @MustBeDocumented("Maximum limit for job information list entries per page") int maximumSize = DEFAULT_MAXIMUM_LIMIT; @@ -189,12 +192,16 @@ private SecHubJobInfoForUserListPage transformToListPage(Page } private void attachJobMetaData(ScheduleSecHubJob job, SecHubJobInfoForUser infoForUser) { - String json = job.getJsonConfiguration(); - if (json == null) { + + // we fetch the unencrypted configuration - but we do only store meta data which + // contains no + // sensitive information. + SecHubConfigurationModel configuration = configurationModelAccess.resolveUnencryptedConfiguration(job); + if (configuration == null) { LOG.error("No sechub configuration found for job: {}. Cannot resolve meta data!", job.getUUID()); return; } - SecHubScanConfiguration configuration = SecHubScanConfiguration.createFromJSON(json); + Optional metaDataOpt = configuration.getMetaData(); if (metaDataOpt.isPresent()) { infoForUser.setMetaData(metaDataOpt.get()); diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepository.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepository.java index ce642399fc..1bd19158e4 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepository.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepository.java @@ -30,9 +30,13 @@ public interface SecHubJobRepository extends JpaRepository getJob(UUID id); - Optional nextJobIdToExecuteFirstInFirstOut(); + Optional nextJobIdToExecuteFirstInFirstOut(Set acceptedEncryptiondPoolIds); + + Optional nextJobIdToExecuteForProjectNotYetExecuted(Set acceptedEncryptiondPoolIds); + + Optional nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(Set acceptedEncryptiondPoolIds); + + /** + * Fetches next jobs which have been canceled or have ended but have an + * encryption pool entry which is lower (means older) than the given one. The + * fetched jobs are returned in a random way. + * + * @param encryptionPoolId the higher (newer) encryption pool id + * @param maxAmount maximum amount of jobs to return + * @return list of jobs, never null + */ + List nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(Long encryptionPoolId, int maxAmount); - Optional nextJobIdToExecuteForProjectNotYetExecuted(); + @Lock(LockModeType.NONE) + public long countCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(Long encryptionPoolId); - Optional nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + /** + * Collects a distinct list of all encryption pool ids which are used by any job + * in any state. + * + * @return set of encryption pool ids, never null + */ + List collectAllUsedEncryptionPoolIdsInsideJobs(); } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java index e3b270f350..7af121d6a5 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryImpl.java @@ -3,7 +3,9 @@ import static com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob.*; +import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; import com.mercedesbenz.sechub.commons.model.job.ExecutionState; @@ -19,11 +21,14 @@ public class SecHubJobRepositoryImpl implements SecHubJobRepositoryCustom { private static final String PARAM_UUID = "p_uuid"; private static final String PARAM_EXECUTION_STATE = "p_exec_state"; private static final String PARAM_EXECUTION_STATE_SUB = "p_sub_exec_state"; + private static final String PARAM_ENCRYPTION_POOL_ID = "p_encrypt_pool_id"; + private static final String PARAM_ENCRYPTION_POOL_IDS = "p_encrypt_pool_ids"; /* @formatter:off */ static final String JPQL_STRING_SELECT_BY_EXECUTION_STATE = "select j from " + CLASS_NAME + " j" + " where j." + PROPERTY_EXECUTION_STATE + " = :" + PARAM_EXECUTION_STATE + + " and j."+ PROPERTY_ENCRYPTION_POOL_ID +" in (:"+PARAM_ENCRYPTION_POOL_IDS+")"+ " order by j." + PROPERTY_CREATED; static final String JPQL_STRING_SELECT_BY_JOB_ID = @@ -38,6 +43,7 @@ public class SecHubJobRepositoryImpl implements SecHubJobRepositoryCustom { static final String JPQL_STRING_SELECT_JOB_WHERE_NOT_YET_RUNNING_SAME_PROJECT = "select j from " + CLASS_NAME + " j" + " where j." + PROPERTY_EXECUTION_STATE + " = :" + PARAM_EXECUTION_STATE + + " and j."+ PROPERTY_ENCRYPTION_POOL_ID +" in (:"+PARAM_ENCRYPTION_POOL_IDS+")"+ " and j." + PROPERTY_PROJECT_ID + " not in ( " + SUB_JPQL_STRING_SELECT_PROJECTS_WITH_RUNNING_JOBS + " )" + " order by " + PROPERTY_CREATED; @@ -51,10 +57,25 @@ public class SecHubJobRepositoryImpl implements SecHubJobRepositoryCustom { static final String JPQL_STRING_SELECT_JOB_WHERE_NOT_YET_RUNNING_SAME_PROJECT_IN_SAME_GROUP = "select j from " + CLASS_NAME + " j" + " where j." + PROPERTY_EXECUTION_STATE + " = :" + PARAM_EXECUTION_STATE + + " and j."+ PROPERTY_ENCRYPTION_POOL_ID +" in (:"+PARAM_ENCRYPTION_POOL_IDS+")"+ " and j." + PROPERTY_PROJECT_ID + " not in ( " + SUB_JPQL_STRING_SELECT_PROJECTS_WITH_RUNNING_JOBS_AND_SAME_MODULE_GROUP + " )" + " order by " + PROPERTY_CREATED; + static final String JPQL_STRING_SELECT_RANDOM_JOB_CANCELED_OR_ENDED_WHERE_POOL_ID_IS_SMALLER_THAN_GIVEN_ONE = + "select j from " + CLASS_NAME + " j" + + " where (j." + PROPERTY_EXECUTION_STATE + " = " + ExecutionState.ENDED + " or j." + PROPERTY_EXECUTION_STATE + " = " +ExecutionState.CANCELED +")"+ + " and j." + PROPERTY_ENCRYPTION_POOL_ID + " < :" + PARAM_ENCRYPTION_POOL_ID + + " order by random()"; + + static final String JPQL_STRING_COUNT_JOBS_CANCELED_OR_ENDED_WHERE_POOL_ID_IS_SMALLER_THAN_GIVEN_ONE = + "select count(j) from " + CLASS_NAME + " j" + + " where (j." + PROPERTY_EXECUTION_STATE + " = " + ExecutionState.ENDED + " or j." + PROPERTY_EXECUTION_STATE + " = " +ExecutionState.CANCELED +")"+ + " and j." + PROPERTY_ENCRYPTION_POOL_ID + " < :" + PARAM_ENCRYPTION_POOL_ID; + + static final String JPQL_STRING_FETCH_ALL_USED_ENCRYPTION_POOL_IDS_IN_JOBS = + "select DISTINCT j."+PROPERTY_ENCRYPTION_POOL_ID+" from " + CLASS_NAME + " j"; + /* @formatter:on */ @@ -75,9 +96,10 @@ public Optional getJob(UUID id) { } @Override - public Optional nextJobIdToExecuteFirstInFirstOut() { + public Optional nextJobIdToExecuteFirstInFirstOut(Set acceptedEncryptiondPoolIds) { Query query = em.createQuery(JPQL_STRING_SELECT_BY_EXECUTION_STATE); query.setParameter(PARAM_EXECUTION_STATE, ExecutionState.READY_TO_START); + query.setParameter(PARAM_ENCRYPTION_POOL_IDS, acceptedEncryptiondPoolIds); query.setMaxResults(1); // we use OPTIMISTIC_FORCE_INCREMENT write lock - so only one POD will be able // to execute next job... @@ -88,10 +110,11 @@ public Optional nextJobIdToExecuteFirstInFirstOut() { } @Override - public Optional nextJobIdToExecuteForProjectNotYetExecuted() { + public Optional nextJobIdToExecuteForProjectNotYetExecuted(Set acceptedEncryptiondPoolIds) { Query query = em.createQuery(JPQL_STRING_SELECT_JOB_WHERE_NOT_YET_RUNNING_SAME_PROJECT); query.setParameter(PARAM_EXECUTION_STATE, ExecutionState.READY_TO_START); query.setParameter(PARAM_EXECUTION_STATE_SUB, ExecutionState.STARTED); + query.setParameter(PARAM_ENCRYPTION_POOL_IDS, acceptedEncryptiondPoolIds); query.setMaxResults(1); query.setLockMode(LockModeType.OPTIMISTIC_FORCE_INCREMENT); @@ -107,14 +130,39 @@ private Optional getUUIDFromJob(Optional job) { } @Override - public Optional nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted() { + public Optional nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(Set acceptedEncryptiondPoolIds) { Query query = em.createQuery(JPQL_STRING_SELECT_JOB_WHERE_NOT_YET_RUNNING_SAME_PROJECT_IN_SAME_GROUP); query.setParameter(PARAM_EXECUTION_STATE, ExecutionState.READY_TO_START); query.setParameter(PARAM_EXECUTION_STATE_SUB, ExecutionState.STARTED); + query.setParameter(PARAM_ENCRYPTION_POOL_IDS, acceptedEncryptiondPoolIds); query.setMaxResults(1); query.setLockMode(LockModeType.OPTIMISTIC_FORCE_INCREMENT); return getUUIDFromJob(typedQuerySupport.getSingleResultAsOptional(query)); } + @Override + public List nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(Long encryptionPoolId, int maxAmount) { + Query query = em.createQuery(JPQL_STRING_SELECT_RANDOM_JOB_CANCELED_OR_ENDED_WHERE_POOL_ID_IS_SMALLER_THAN_GIVEN_ONE); + query.setParameter(PARAM_ENCRYPTION_POOL_ID, encryptionPoolId); + query.setMaxResults(maxAmount); + query.setLockMode(LockModeType.OPTIMISTIC_FORCE_INCREMENT); + + return typedQuerySupport.getList(query); + } + + public long countCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(Long encryptionPoolId) { + Query query = em.createQuery(JPQL_STRING_COUNT_JOBS_CANCELED_OR_ENDED_WHERE_POOL_ID_IS_SMALLER_THAN_GIVEN_ONE); + query.setParameter(PARAM_ENCRYPTION_POOL_ID, encryptionPoolId); + query.setMaxResults(1); + return (Long) query.getSingleResult(); + } + + @SuppressWarnings("unchecked") + @Override + public List collectAllUsedEncryptionPoolIdsInsideJobs() { + Query query = em.createQuery(JPQL_STRING_FETCH_ALL_USED_ENCRYPTION_POOL_IDS_IN_JOBS); + return query.getResultList(); + } + } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobTransactionService.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobTransactionService.java index 35ca02314e..2ca60c7998 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobTransactionService.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobTransactionService.java @@ -3,6 +3,7 @@ import static com.mercedesbenz.sechub.sharedkernel.util.Assert.*; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -44,4 +45,16 @@ public void updateExecutionStateInOwnTransaction(UUID sechubJobUUID, ExecutionSt LOG.info("Job :{} has now execution state: {}", sechubJobUUID, job.getExecutionState()); } + public void saveInOwnTransaction(ScheduleSecHubJob job) { + repository.save(job); + } + + public long countCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(Long latestPoolid) { + return repository.countCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(latestPoolid); + } + + public List nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(Long latestPoolid, int updateBlockSize) { + return repository.nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(latestPoolid, updateBlockSize); + } + } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java index b66a14773c..a56b4d4bfb 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategy.java @@ -2,11 +2,13 @@ package com.mercedesbenz.sechub.domain.schedule.strategy; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; @Component @@ -15,6 +17,9 @@ public class FirstComeFirstServeSchedulerStrategy implements SchedulerStrategy { @Autowired public SecHubJobRepository jobRepository; + @Autowired + ScheduleEncryptionService encryptionService; + @Override public SchedulerStrategyId getSchedulerId() { return SchedulerStrategyId.FIRST_COME_FIRST_SERVE; @@ -22,8 +27,9 @@ public SchedulerStrategyId getSchedulerId() { @Override public UUID nextJobId() { + Set supportedPoolIds = encryptionService.getCurrentEncryptionPoolIds(); - Optional nextJob = jobRepository.nextJobIdToExecuteFirstInFirstOut(); + Optional nextJob = jobRepository.nextJobIdToExecuteFirstInFirstOut(supportedPoolIds); if (!nextJob.isPresent()) { return null; } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java index 8dba927e75..67125fdee1 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy.java @@ -2,11 +2,13 @@ package com.mercedesbenz.sechub.domain.schedule.strategy; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; /** @@ -52,6 +54,9 @@ public class OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy implements Sc @Autowired SecHubJobRepository jobRepository; + @Autowired + ScheduleEncryptionService encryptionService; + @Override public SchedulerStrategyId getSchedulerId() { return SchedulerStrategyId.ONE_SCAN_PER_PROJECT_AND_MODULE_GROUP; @@ -59,7 +64,9 @@ public SchedulerStrategyId getSchedulerId() { @Override public UUID nextJobId() { - Optional nextJob = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Set supportedPoolIds = encryptionService.getCurrentEncryptionPoolIds(); + + Optional nextJob = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(supportedPoolIds); if (!nextJob.isPresent()) { return null; } diff --git a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java index 572c3824a8..cb76a706f7 100644 --- a/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java +++ b/sechub-schedule/src/main/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategy.java @@ -2,11 +2,13 @@ package com.mercedesbenz.sechub.domain.schedule.strategy; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; @Component @@ -15,6 +17,9 @@ public class OnlyOneScanPerProjectAtSameTimeStrategy implements SchedulerStrateg @Autowired SecHubJobRepository jobRepository; + @Autowired + ScheduleEncryptionService encryptionService; + @Override public SchedulerStrategyId getSchedulerId() { return SchedulerStrategyId.ONE_SCAN_PER_PROJECT; @@ -22,7 +27,9 @@ public SchedulerStrategyId getSchedulerId() { @Override public UUID nextJobId() { - Optional nextJob = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(); + Set supportedPoolIds = encryptionService.getCurrentEncryptionPoolIds(); + + Optional nextJob = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(supportedPoolIds); if (!nextJob.isPresent()) { return null; } diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupServiceTest.java index 139f22b00b..787046919d 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupServiceTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/autocleanup/ScheduleAutoCleanupServiceTest.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.schedule.autocleanup; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -12,11 +13,13 @@ import org.mockito.ArgumentCaptor; import com.mercedesbenz.sechub.domain.schedule.config.SchedulerConfigService; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleCipherPoolCleanupService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobDataRepository; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; import com.mercedesbenz.sechub.sharedkernel.TimeCalculationService; import com.mercedesbenz.sechub.sharedkernel.autocleanup.AutoCleanupResult; import com.mercedesbenz.sechub.sharedkernel.autocleanup.AutoCleanupResultInspector; +import com.mercedesbenz.sechub.test.TestCanaryException; class ScheduleAutoCleanupServiceTest { @@ -26,6 +29,7 @@ class ScheduleAutoCleanupServiceTest { private SecHubJobDataRepository jobDataRepository; private TimeCalculationService timeCalculationService; private AutoCleanupResultInspector inspector; + private ScheduleCipherPoolCleanupService encryptionPoolCleanupService; @BeforeEach void beforeEach() { @@ -36,12 +40,64 @@ void beforeEach() { jobDataRepository = mock(SecHubJobDataRepository.class); timeCalculationService = mock(TimeCalculationService.class); inspector = mock(AutoCleanupResultInspector.class); + encryptionPoolCleanupService = mock(ScheduleCipherPoolCleanupService.class); serviceToTest.configService = configService; serviceToTest.jobRepository = jobRepository; serviceToTest.jobDataRepository = jobDataRepository; serviceToTest.timeCalculationService = timeCalculationService; serviceToTest.inspector = inspector; + serviceToTest.encryptionPoolCleanupService = encryptionPoolCleanupService; + } + + @Test + void auto_cleanup_triggers_encryption_pool_cleanup() throws Exception { + + /* prepare */ + when(configService.getAutoCleanupInDays()).thenReturn(1L); + LocalDateTime cleanTime = LocalDateTime.now().minusDays(1L); + when(timeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); + + /* execute */ + serviceToTest.cleanup(); + + /* test */ + verify(encryptionPoolCleanupService).cleanupCipherPoolDataIfNecessaryAndPossible(); + + } + + @Test + void when_jobDataRepository_deleteJobDataOlderThan_throws_exception_encryption_pool_cleanup_is_not_done() throws Exception { + + /* prepare */ + when(configService.getAutoCleanupInDays()).thenReturn(1L); + LocalDateTime cleanTime = LocalDateTime.now().minusDays(1L); + when(timeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); + when(jobDataRepository.deleteJobDataOlderThan(cleanTime)).thenThrow(TestCanaryException.class); + + /* execute */ + assertThatThrownBy(()->serviceToTest.cleanup()).isInstanceOf(TestCanaryException.class); + + /* test */ + verify(encryptionPoolCleanupService,never()).cleanupCipherPoolDataIfNecessaryAndPossible(); + + } + + @Test + void when_jobRepository_deleteJobsOlderThan_throws_exception_encryption_pool_cleanup_is_not_done() throws Exception { + + /* prepare */ + when(configService.getAutoCleanupInDays()).thenReturn(1L); + LocalDateTime cleanTime = LocalDateTime.now().minusDays(1L); + when(timeCalculationService.calculateNowMinusDays(any())).thenReturn(cleanTime); + when(jobRepository.deleteJobsOlderThan(cleanTime)).thenThrow(TestCanaryException.class); + + /* execute */ + assertThatThrownBy(()->serviceToTest.cleanup()).isInstanceOf(TestCanaryException.class); + + /* test */ + verify(encryptionPoolCleanupService,never()).cleanupCipherPoolDataIfNecessaryAndPossible(); + } @Test @@ -62,6 +118,8 @@ void cleanup_executes_NOT_delete_job_information_for_0_days() { verify(jobDataRepository, never()).deleteJobDataOlderThan(any()); // check inspection as expected: never because not executed verify(inspector, never()).inspect(any()); + // check not encryption pool cleanup is done + verify(encryptionPoolCleanupService, never()).cleanupCipherPoolDataIfNecessaryAndPossible(); } @Test diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherAlgorithmTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherAlgorithmTest.java new file mode 100644 index 0000000000..2c0a5e0af2 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherAlgorithmTest.java @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; + +class ScheduleCipherAlgorithmTest { + + /* + * We have separated the database enumeration from the encryption parts - for + * different reasons. The test will ensure that the types are as expected. + */ + @ParameterizedTest + @ArgumentsSource(CipherAlgorithmTestData.class) + void databaseAlgorithmHasExpectedInternalType(SecHubCipherAlgorithm algorithmInDb, PersistentCipherType internalType) { + assertThat(algorithmInDb.getType()).isEqualTo(internalType); + } + + private static class CipherAlgorithmTestData implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + /* @formatter:off */ + return Stream.of( + Arguments.of(SecHubCipherAlgorithm.NONE, PersistentCipherType.NONE), + Arguments.of(SecHubCipherAlgorithm.AES_GCM_SIV_128, PersistentCipherType.AES_GCM_SIV_128), + Arguments.of(SecHubCipherAlgorithm.AES_GCM_SIV_256, PersistentCipherType.AES_GCM_SIV_256) + ) + ; + /* @formatter:on */ + } + + } +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupServiceTest.java new file mode 100644 index 0000000000..0ca426a1ff --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolCleanupServiceTest.java @@ -0,0 +1,276 @@ +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; + +class ScheduleCipherPoolCleanupServiceTest { + + private static final long POOL_ID_0 = 0L; + private static final long POOL_ID_3 = 3L; + private static final long POOL_ID_2 = 2L; + private static final long POOL_ID_1 = 1L; + private ScheduleCipherPoolCleanupService serviceToTest; + private ScheduleEncryptionService encryptionService; + private SecHubJobRepository jobRepository; + private ScheduleCipherPoolDataRepository poolDataRepository; + private ScheduleLatestCipherPoolDataCalculator latestCipherPoolDataCalculator; + private SecHubOutdatedEncryptionPoolSupport outdatedEncryptionPoolSupport; + + @BeforeEach + public void beforeEach() throws Exception { + serviceToTest = new ScheduleCipherPoolCleanupService(); + + encryptionService = mock(ScheduleEncryptionService.class); + jobRepository = mock(SecHubJobRepository.class); + poolDataRepository = mock(ScheduleCipherPoolDataRepository.class); + latestCipherPoolDataCalculator = mock(ScheduleLatestCipherPoolDataCalculator.class); + outdatedEncryptionPoolSupport = mock(SecHubOutdatedEncryptionPoolSupport.class); + + serviceToTest.encryptionService = encryptionService; + serviceToTest.jobRepository = jobRepository; + serviceToTest.poolDataRepository = poolDataRepository; + serviceToTest.latestCipherPoolDataCalculator = latestCipherPoolDataCalculator; + serviceToTest.outdatedEncryptionPoolSupport = outdatedEncryptionPoolSupport; + } + + @Test + void when_cluster_could_be_outdated_no_further_inspection_is_done() throws Exception { + + /* prepare */ + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()).thenReturn(true); + + /* execute */ + serviceToTest.cleanupCipherPoolDataIfNecessaryAndPossible(); + + /* test */ + verify(outdatedEncryptionPoolSupport).isOutdatedEncryptionPoolPossibleInCluster(); + + verifyNoInteractions(encryptionService); + verifyNoInteractions(jobRepository); + verifyNoInteractions(poolDataRepository); + verifyNoInteractions(latestCipherPoolDataCalculator); + + } + + @Test + void no_pool_data_found_nothing_else_is_done() throws Exception { + + /* prepare */ + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()).thenReturn(false); + List list = new ArrayList<>(); + when(poolDataRepository.findAll()).thenReturn(list); + + /* execute */ + serviceToTest.cleanupCipherPoolDataIfNecessaryAndPossible(); + + /* test */ + verify(outdatedEncryptionPoolSupport).isOutdatedEncryptionPoolPossibleInCluster(); + verify(poolDataRepository).findAll(); + + verifyNoInteractions(encryptionService); + verifyNoInteractions(jobRepository); + verifyNoInteractions(latestCipherPoolDataCalculator); + + } + + @Test + void pool_entries_exist_but_encryption_pool_has_not_latest_no_jobs_are_inspected() throws Exception { + + /* prepare */ + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()).thenReturn(false); + List list = new ArrayList<>(); + ScheduleCipherPoolData poolData1 = createTestPoolData(POOL_ID_1); + list.add(poolData1); + + ScheduleCipherPoolData poolData2 = createTestPoolData(POOL_ID_2); + list.add(poolData2); + + when(poolDataRepository.findAll()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(poolData2); + + when(encryptionService.getLatestCipherPoolId()).thenReturn(POOL_ID_1); // different than calculated + + /* execute */ + serviceToTest.cleanupCipherPoolDataIfNecessaryAndPossible(); + + /* test */ + verify(encryptionService).getLatestCipherPoolId(); + verify(outdatedEncryptionPoolSupport).isOutdatedEncryptionPoolPossibleInCluster(); + verify(poolDataRepository).findAll(); + verify(latestCipherPoolDataCalculator).calculateLatestPoolData(list); + + verifyNoInteractions(jobRepository); + + } + + @Test + void no_unused_pool_entries() throws Exception { + + /* prepare */ + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()).thenReturn(false); + List list = new ArrayList<>(); + ScheduleCipherPoolData poolData1 = createTestPoolData(POOL_ID_1); + list.add(poolData1); + + ScheduleCipherPoolData poolData2 = createTestPoolData(POOL_ID_2); + list.add(poolData2); + + when(poolDataRepository.findAll()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(poolData2); + + when(encryptionService.getLatestCipherPoolId()).thenReturn(POOL_ID_2); + + List collectList = List.of(POOL_ID_1, POOL_ID_2); + when(jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs()).thenReturn(collectList); + + /* execute */ + serviceToTest.cleanupCipherPoolDataIfNecessaryAndPossible(); + + /* test */ + verify(outdatedEncryptionPoolSupport).isOutdatedEncryptionPoolPossibleInCluster(); + verify(poolDataRepository).findAll(); + verify(latestCipherPoolDataCalculator).calculateLatestPoolData(list); + + verify(jobRepository).collectAllUsedEncryptionPoolIdsInsideJobs(); + + verify(poolDataRepository, never()).delete(any(ScheduleCipherPoolData.class)); + ; + + } + + @Test + void unused_pool_entry_latest_is_used() throws Exception { + + /* prepare */ + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()).thenReturn(false); + List list = new ArrayList<>(); + ScheduleCipherPoolData poolData1 = createTestPoolData(POOL_ID_1); + list.add(poolData1); + + ScheduleCipherPoolData poolData2 = createTestPoolData(POOL_ID_2); + list.add(poolData2); + + ScheduleCipherPoolData poolData3 = createTestPoolData(POOL_ID_3); + list.add(poolData3); + + when(poolDataRepository.findAll()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(poolData3); + + when(encryptionService.getLatestCipherPoolId()).thenReturn(POOL_ID_3); + + List collectList = List.of(POOL_ID_2, POOL_ID_3); + when(jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs()).thenReturn(collectList); + + /* execute */ + serviceToTest.cleanupCipherPoolDataIfNecessaryAndPossible(); + + /* test */ + verify(outdatedEncryptionPoolSupport).isOutdatedEncryptionPoolPossibleInCluster(); + verify(poolDataRepository).findAll(); + verify(latestCipherPoolDataCalculator).calculateLatestPoolData(list); + + verify(jobRepository).collectAllUsedEncryptionPoolIdsInsideJobs(); + + verify(poolDataRepository).delete(poolData1); + verify(poolDataRepository, never()).delete(poolData2); + verify(poolDataRepository, never()).delete(poolData3); + + } + + @Test + void unused_pool_entry_latest_is_NOT_used() throws Exception { + + /* prepare */ + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()).thenReturn(false); + List list = new ArrayList<>(); + ScheduleCipherPoolData poolData1 = createTestPoolData(POOL_ID_1); + list.add(poolData1); + + ScheduleCipherPoolData poolData2 = createTestPoolData(POOL_ID_2); + list.add(poolData2); + + ScheduleCipherPoolData poolData3 = createTestPoolData(POOL_ID_3); + list.add(poolData3); + + when(poolDataRepository.findAll()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(poolData3); + + when(encryptionService.getLatestCipherPoolId()).thenReturn(POOL_ID_3); + + List collectList = List.of(POOL_ID_2); + when(jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs()).thenReturn(collectList); + + /* execute */ + serviceToTest.cleanupCipherPoolDataIfNecessaryAndPossible(); + + /* test */ + verify(outdatedEncryptionPoolSupport).isOutdatedEncryptionPoolPossibleInCluster(); + verify(poolDataRepository).findAll(); + verify(latestCipherPoolDataCalculator).calculateLatestPoolData(list); + + verify(jobRepository).collectAllUsedEncryptionPoolIdsInsideJobs(); + + verify(poolDataRepository).delete(poolData1); + verify(poolDataRepository, never()).delete(poolData2); + verify(poolDataRepository, never()).delete(poolData3);// is not used, but latest ,so not deleted + + } + + @Test + void two_unused_pool_entries_latest_is_NOT_used() throws Exception { + + /* prepare */ + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionPoolPossibleInCluster()).thenReturn(false); + List list = new ArrayList<>(); + ScheduleCipherPoolData poolData0 = createTestPoolData(POOL_ID_0); + list.add(poolData0); + + ScheduleCipherPoolData poolData1 = createTestPoolData(POOL_ID_1); + list.add(poolData1); + + ScheduleCipherPoolData poolData2 = createTestPoolData(POOL_ID_2); + list.add(poolData2); + + ScheduleCipherPoolData poolData3 = createTestPoolData(POOL_ID_3); + list.add(poolData3); + + when(poolDataRepository.findAll()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(poolData3); + + when(encryptionService.getLatestCipherPoolId()).thenReturn(POOL_ID_3); + + List collectList = List.of(POOL_ID_2); + when(jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs()).thenReturn(collectList); + + /* execute */ + serviceToTest.cleanupCipherPoolDataIfNecessaryAndPossible(); + + /* test */ + verify(outdatedEncryptionPoolSupport).isOutdatedEncryptionPoolPossibleInCluster(); + verify(poolDataRepository).findAll(); + verify(latestCipherPoolDataCalculator).calculateLatestPoolData(list); + + verify(jobRepository).collectAllUsedEncryptionPoolIdsInsideJobs(); + + verify(poolDataRepository).delete(poolData0); + verify(poolDataRepository).delete(poolData1); + verify(poolDataRepository, never()).delete(poolData2); + verify(poolDataRepository, never()).delete(poolData3);// is not used, but latest ,so not deleted + + } + + private ScheduleCipherPoolData createTestPoolData(long poolId) { + ScheduleCipherPoolData poolData1 = mock(ScheduleCipherPoolData.class, "Test pool data:" + poolId); + when(poolData1.getId()).thenReturn(poolId); + return poolData1; + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataProviderSpringBootTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataProviderSpringBootTest.java new file mode 100644 index 0000000000..b9af586b00 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataProviderSpringBootTest.java @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; + +@SpringBootTest(classes = ScheduleCipherPoolDataProvider.class) +class ScheduleCipherPoolDataProviderSpringBootTest { + + @Autowired + private ScheduleCipherPoolDataProvider providerToTest; + + @MockBean + private ScheduleCipherPoolDataRepository repository; + + @BeforeEach + void beforeEach() { + } + + @Test + void provider_creates_fallback_when_nothing_found() { + + /* prepare */ + when(repository.findAll()).thenReturn(Collections.emptyList()); + + /* execute */ + List result = providerToTest.ensurePoolDataAvailable(); + + /* test */ + assertThat(result).isNotEmpty().hasSize(1); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(ScheduleCipherPoolData.class); + verify(repository).save(argumentCaptor.capture()); + + ScheduleCipherPoolData savedFallback = argumentCaptor.getValue(); + + assertThat(savedFallback).isNotNull(); + + assertThat(savedFallback.getAlgorithm()).isEqualTo(SecHubCipherAlgorithm.NONE); + assertThat(savedFallback.getTestEncrypted()).isNotNull(); + assertThat(savedFallback.getCreated()).isNotNull(); + assertThat(savedFallback.getCreatedFrom()).describedAs("Created by system means always null because no user involved").isNull(); + assertThat(savedFallback.getId()).describedAs("Fallback is created as first entry when none exists, will always use the first possible one") + .isEqualTo(0); + assertThat(savedFallback.getPasswordSourceType()).isEqualTo(SecHubCipherPasswordSourceType.NONE); + assertThat(savedFallback.getPasswordSourceData()).isNull(); + assertThat(savedFallback.getTestEncrypted()).isEqualTo("fallback".getBytes(Charset.forName("UTF-8"))); + assertThat(savedFallback.getTestInitialVector()).isNull(); + assertThat(savedFallback.getTestText()).isEqualTo("fallback"); + + } + + @Test + void provider_returns_found_data_without_fallback_when_data_available() { + + ScheduleCipherPoolData data1 = mock(ScheduleCipherPoolData.class); + ScheduleCipherPoolData data2 = mock(ScheduleCipherPoolData.class); + + /* prepare */ + when(repository.findAll()).thenReturn(List.of(data1, data2)); + + /* execute */ + List result = providerToTest.ensurePoolDataAvailable(); + + /* test */ + assertThat(result).hasSize(2).contains(data1, data2); + + } + + @Test + void isContainingExactlyGivenPoolIds_null_throws_illegal_argument() { + + /* execute +test */ + assertThatThrownBy(() -> providerToTest.isContainingExactlyGivenPoolIds(null)).isInstanceOf(IllegalArgumentException.class); + + } + + @ParameterizedTest + @ArgumentsSource(SimplePoolIdSetArgumentsProvider.class) + void isContainingExactlyGivenPoolIds_contains_same_as_expected(Set poolIdsInDbAndCurrentlyInMemory) { + + /* prepare */ + when(repository.fetchAllCipherPoolIds()).thenReturn(poolIdsInDbAndCurrentlyInMemory); + + /* execute */ + boolean result = providerToTest.isContainingExactlyGivenPoolIds(poolIdsInDbAndCurrentlyInMemory); + + /* test */ + assertThat(result).isTrue(); + } + + @ParameterizedTest + @ArgumentsSource(DifferentPoolIdSetArgumentsProvider.class) + void isContainingExactlyGivenPoolIds_not_same(Set currentPoolIds, Set poolIdsInDatabase) { + + /* prepare */ + when(repository.fetchAllCipherPoolIds()).thenReturn(poolIdsInDatabase); + + /* execute */ + boolean result = providerToTest.isContainingExactlyGivenPoolIds(currentPoolIds); + + /* test */ + assertThat(result).isFalse(); + } + + static class SimplePoolIdSetArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of(Arguments.of(Set.of()), Arguments.of(Set.of(0L)), Arguments.of(Set.of(0L, 1L, 2L)), Arguments.of(Set.of(0L, 1L, 2L, 3L, 5L)), + Arguments.of(Set.of(3L, 5L))); + } + + } + + static class DifferentPoolIdSetArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of(Arguments.of(Set.of(), Set.of(1L)), Arguments.of(Set.of(0L), Set.of()), Arguments.of(Set.of(0L), Set.of(1L)), + Arguments.of(Set.of(0L, 1L, 2L), Set.of(0L, 1l)), Arguments.of(Set.of(0L, 1L, 2L, 3L, 5L), Set.of(0L, 1L, 2L, 3L, 6L)), + Arguments.of(Set.of(3L, 5L), Set.of(5L, 6L))); + } + + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataRepositoryDBTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataRepositoryDBTest.java new file mode 100644 index 0000000000..3553ee3efb --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataRepositoryDBTest.java @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; + +import java.nio.charset.Charset; +import java.time.LocalDateTime; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; + +@RunWith(SpringRunner.class) +@DataJpaTest +@ContextConfiguration(classes = { ScheduleCipherPoolDataRepository.class, ScheduleCipherPoolDataRepositoryDBTest.SimpleTestConfiguration.class }) +class ScheduleCipherPoolDataRepositoryDBTest { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private ScheduleCipherPoolDataRepository repositoryToTest; + + @Test + void fetchAllCipherPoolIds_nothing_found_returns_empty_set() { + + /* execute */ + Set result = repositoryToTest.fetchAllCipherPoolIds(); + + /* test */ + assertThat(result).isNotNull().isEmpty(); + } + + @Test + void fetchAllCipherPoolIds_one_entry() throws Exception { + + /* prepare */ + Set createdIds = new LinkedHashSet<>(); + + createdIds.add(createEntry()); + + /* execute */ + Set result = repositoryToTest.fetchAllCipherPoolIds(); + + /* test */ + assertThat(result).isNotNull().isNotEmpty().containsAll(createdIds).hasSize(1); + + } + + @Test + void fetchAllCipherPoolIds_two_entries() throws Exception { + /* prepare */ + Set createdIds = new LinkedHashSet<>(); + + createdIds.add(createEntry()); + createdIds.add(createEntry()); + + /* execute */ + Set result = repositoryToTest.fetchAllCipherPoolIds(); + + /* test */ + assertThat(result).isNotNull().isNotEmpty().containsAll(createdIds); + + } + + private long createEntry() { + ScheduleCipherPoolData data1 = new ScheduleCipherPoolData(); + data1.secHubCipherPasswordSourceType = SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE; + data1.created = LocalDateTime.now(); + data1.algorithm = SecHubCipherAlgorithm.NONE; + data1.testEncrypted = "test".getBytes(Charset.forName("UTF-8")); + data1.testInitialVector = new byte[] {}; + data1.testText = "test"; + + ScheduleCipherPoolData result = entityManager.persist(data1); + return result.getId(); + } + + @TestConfiguration + @EnableAutoConfiguration + public static class SimpleTestConfiguration { + + } +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataTransactionServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataTransactionServiceTest.java new file mode 100644 index 0000000000..890febc8fd --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleCipherPoolDataTransactionServiceTest.java @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ScheduleCipherPoolDataTransactionServiceTest { + + private ScheduleCipherPoolDataTransactionService serviceToTest; + private ScheduleCipherPoolDataRepository repository; + + @BeforeEach + void beforeEach() { + + repository = mock(ScheduleCipherPoolDataRepository.class); + + serviceToTest = new ScheduleCipherPoolDataTransactionService(); + + serviceToTest.repository = repository; + + } + + @Test + void storeInOwnTransaction_calls_repository_save() throws Exception { + + /* prepare */ + ScheduleCipherPoolData data = mock(ScheduleCipherPoolData.class); + + /* execute */ + serviceToTest.storeInOwnTransaction(data); + + /* test */ + verify(repository).save(data); + + } + + @Test + void storeInOwnTransaction_returns_repository_object() throws Exception { + /* prepare */ + ScheduleCipherPoolData data = mock(ScheduleCipherPoolData.class); + when(repository.save(data)).thenReturn(data); + + /* execute */ + ScheduleCipherPoolData result = serviceToTest.storeInOwnTransaction(data); + + /* test */ + assertThat(result).isEqualTo(data); + + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolFactoryTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolFactoryTest.java new file mode 100644 index 0000000000..a05529b9dd --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolFactoryTest.java @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.nio.charset.Charset; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubSecretKeyProviderFactory; + +class ScheduleEncryptionPoolFactoryTest { + private ScheduleEncryptionPoolFactory factoryToTest; + private PersistentCipherFactory cipherFactory; + private EncryptionSupport encryptionSupport; + private SecHubSecretKeyProviderFactory secHubSecretKeyProviderFactory; + private SecretKeyProvider noneSecretKeyProvider; + private InitializationVector noneInitVector1; + private PersistentCipher noneCipher1; + private byte[] noneInitVector1Bytes;; + + @BeforeEach + void beforeEach() { + factoryToTest = new ScheduleEncryptionPoolFactory(); + + cipherFactory = mock(PersistentCipherFactory.class); + encryptionSupport = mock(EncryptionSupport.class); + secHubSecretKeyProviderFactory = mock(SecHubSecretKeyProviderFactory.class); + + factoryToTest.cipherFactory = cipherFactory; + factoryToTest.encryptionSupport = encryptionSupport; + factoryToTest.secHubSecretKeyProviderFactory = secHubSecretKeyProviderFactory; + + noneSecretKeyProvider = mock(SecretKeyProvider.class, "none-secret-keyprovider"); + when(secHubSecretKeyProviderFactory.createSecretKeyProvider(PersistentCipherType.NONE, SecHubCipherPasswordSourceType.NONE, null)) + .thenReturn(noneSecretKeyProvider); + + noneCipher1 = mock(PersistentCipher.class, "none-cipher1"); + when(cipherFactory.createCipher(noneSecretKeyProvider, PersistentCipherType.NONE)).thenReturn(noneCipher1); + when(noneCipher1.createNewInitializationVector()).thenReturn(noneInitVector1); + noneInitVector1 = mock(InitializationVector.class); + noneInitVector1Bytes = new byte[] {}; + when(noneInitVector1.getInitializationBytes()).thenReturn(noneInitVector1Bytes); + + } + + @Test + void createEncryptionPool_null_throws_illegal_argument() throws Exception { + + assertThatThrownBy(() -> factoryToTest.createEncryptionPool(null)).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("never be null"); + } + + @Test + void createEncryptionPool_empty_map_is_accepted() throws Exception { + + /* execute */ + ScheduleEncryptionPool pool = factoryToTest.createEncryptionPool(Collections.emptyList()); + + /* test */ + assertThat(pool).isNotNull(); + assertThat(pool.getCipherForPoolId(Long.valueOf(0))).isNull(); + + } + + @Test + void createEncryptionPool_map_with_valid_entry0_is_accepted() throws Exception { + + /* prepare */ + String plainText = "testdata-plaintext"; + byte[] noneEncrypted = plainText.getBytes(Charset.forName("UTF-8")); + List list = new ArrayList<>(); + + // create valid cipher pool data + ScheduleCipherPoolData data1 = new ScheduleCipherPoolData(); + data1.algorithm = SecHubCipherAlgorithm.NONE; + data1.secHubCipherPasswordSourceType = SecHubCipherPasswordSourceType.NONE; + data1.created = LocalDateTime.now(); + data1.createdFrom = "user1"; + data1.id = Long.valueOf(0); + data1.testEncrypted = noneEncrypted; + data1.testText = plainText; + data1.testInitialVector = noneInitVector1Bytes; + + list.add(data1); + + // simulate encryption - needed for internal validation + when(encryptionSupport.decryptString(eq(noneEncrypted), eq(noneCipher1), any(InitializationVector.class))).thenReturn(plainText); + + /* execute */ + ScheduleEncryptionPool pool = factoryToTest.createEncryptionPool(list); + + /* test */ + assertThat(pool).isNotNull(); + assertThat(pool.getCipherForPoolId(Long.valueOf(0))).isSameAs(noneCipher1); + + ArgumentCaptor initVectorCaptor = ArgumentCaptor.forClass(InitializationVector.class); + verify(encryptionSupport).decryptString(eq(noneEncrypted), eq(noneCipher1), initVectorCaptor.capture()); + + InitializationVector usedVector = initVectorCaptor.getValue(); + assertThat(usedVector.getInitializationBytes()).isEqualTo(noneInitVector1Bytes); + + } + + @Test + void createEncryptionPool_map_with_valid_entry0_but_wrong_decryption_is_NOT_accepted_throws_exception() throws Exception { + + /* prepare */ + String plainText = "testdata-plaintext"; + byte[] noneEncrypted = plainText.getBytes(Charset.forName("UTF-8")); + List list = new ArrayList<>(); + + // create valid cipher pool data + ScheduleCipherPoolData data1 = new ScheduleCipherPoolData(); + data1.algorithm = SecHubCipherAlgorithm.NONE; + data1.secHubCipherPasswordSourceType = SecHubCipherPasswordSourceType.NONE; + data1.created = LocalDateTime.now(); + data1.createdFrom = "user1"; + data1.id = Long.valueOf(0); + data1.testEncrypted = noneEncrypted; + data1.testText = plainText; + data1.testInitialVector = noneInitVector1Bytes; + + list.add(data1); + + // simulate encryption - needed for internal validation + when(encryptionSupport.decryptString(eq(noneEncrypted), eq(noneCipher1), any(InitializationVector.class))).thenReturn("wrong decrypted"); + + /* execute + test @formatter:off */ + assertThatThrownBy(() -> factoryToTest.createEncryptionPool(list)). + isInstanceOf(ScheduleEncryptionException.class). + hasMessageContaining("cipher pool"). + hasMessageContaining("cannot be handled"); + /* @formatter:on */ + + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolTest.java new file mode 100644 index 0000000000..1a0e24194b --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionPoolTest.java @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; + +class ScheduleEncryptionPoolTest { + + @Test + void constructor_from_map_results_in_expected_getCipherForPoolId_results() { + /* prepare */ + Map map = new HashMap<>(); + PersistentCipher cipher1 = mock(PersistentCipher.class); + PersistentCipher cipher2 = mock(PersistentCipher.class); + map.put(Long.valueOf(1), cipher1); + map.put(Long.valueOf(2), cipher2); + + /* execute */ + ScheduleEncryptionPool poolToTest = new ScheduleEncryptionPool(map); + + /* test */ + assertThat(poolToTest.getCipherForPoolId(Long.valueOf(0))).isNull(); + assertThat(poolToTest.getCipherForPoolId(Long.valueOf(1))).isEqualTo(cipher1); + assertThat(poolToTest.getCipherForPoolId(Long.valueOf(2))).isEqualTo(cipher2); + assertThat(poolToTest.getCipherForPoolId(Long.valueOf(3))).isNull(); + } + + @ParameterizedTest + @ValueSource(longs = { 0, -1, 99 }) + void constructor_from_null_map_results_in_pool_but_getCipherForPoolId_returns_always_null(long value) { + /* prepare */ + Map map = null; + + /* execute */ + ScheduleEncryptionPool poolToTest = new ScheduleEncryptionPool(map); + + /* test */ + assertThat(poolToTest.getCipherForPoolId(Long.valueOf(value))).isNull(); + } + + @ParameterizedTest + @ValueSource(longs = { 0, -1, 99 }) + void constructor_from_empty_map_results_in_pool_but_getCipherForPoolId_returns_always_null(long value) { + /* prepare */ + Map map = new HashMap<>(); + + /* execute */ + ScheduleEncryptionPool poolToTest = new ScheduleEncryptionPool(map); + + /* test */ + assertThat(poolToTest.getCipherForPoolId(Long.valueOf(value))).isNull(); + } + + @Test + void getAllPoolIds_returns_keys_of_pool_map() { + /* prepare */ + Map map = new HashMap<>(); + PersistentCipher cipher1 = mock(PersistentCipher.class); + PersistentCipher cipher2 = mock(PersistentCipher.class); + map.put(Long.valueOf(1), cipher1); + map.put(Long.valueOf(2), cipher2); + + ScheduleEncryptionPool poolToTest = new ScheduleEncryptionPool(map); + + /* execute */ + assertThat(poolToTest.getAllPoolIds()).contains(1L, 2L).hasSize(2); + } +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionResultTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionResultTest.java new file mode 100644 index 0000000000..0fcc622ea4 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionResultTest.java @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; + +class ScheduleEncryptionResultTest { + + @ParameterizedTest + @ArgumentsSource(IllegalSchedulerEncryptionResultParameters.class) + void constructor_with_invalid_arguments_throws_illegal_argument_exception(Long poolId, EncryptionResult encryptionResult) { + + assertThatThrownBy(() -> new ScheduleEncryptionResult(poolId, encryptionResult)).isInstanceOf(IllegalArgumentException.class); + + } + + @Test + void enryption_result_with_valid_parameters_contains_the_information() { + Long poolId = Long.valueOf(0); + EncryptionResult encryptionResult = createValidEncryptionResult(); + + /* execute */ + ScheduleEncryptionResult scheduleEncryptionResult = new ScheduleEncryptionResult(poolId, encryptionResult); + + /* test */ + assertThat(scheduleEncryptionResult).isNotNull(); + assertThat(scheduleEncryptionResult.getEncryptedData()).isEqualTo(encryptionResult.getEncryptedData()); + assertThat(scheduleEncryptionResult.getInitialVector()).isEqualTo(encryptionResult.getInitialVector()); + assertThat(scheduleEncryptionResult.getCipherPoolId()).isEqualTo(poolId); + + } + + private static class IllegalSchedulerEncryptionResultParameters implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of(Arguments.of(null, null), Arguments.of(Long.valueOf(0), null), Arguments.of(null, createValidEncryptionResult()) + + ); + } + + } + + private static EncryptionResult createValidEncryptionResult() { + return new EncryptionResult("xxx".getBytes(), new InitializationVector("vector".getBytes())); + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionRotationServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionRotationServiceTest.java new file mode 100644 index 0000000000..a6d72a38e1 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionRotationServiceTest.java @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; + +class ScheduleEncryptionRotationServiceTest { + + private ScheduleEncryptionRotationService serviceToTest; + private ScheduleEncryptionService encryptionService; + private ScheduleCipherPoolDataTransactionService transactionService; + + @BeforeEach + void beforeEach() { + + encryptionService = mock(ScheduleEncryptionService.class); + + serviceToTest = new ScheduleEncryptionRotationService(); + + transactionService = mock(ScheduleCipherPoolDataTransactionService.class); + serviceToTest.transactionService = transactionService; + serviceToTest.encryptionService = encryptionService; + + } + + @Test + void createInitialCipherPoolData_stores_initial_cipher_pooldata_from_encryption_service_together_with_user_info() throws Exception { + + /* prepare */ + SecHubEncryptionData data = new SecHubEncryptionData(); + data.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_128); + data.setPasswordSourceData("data1"); + data.setPasswordSourceType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + + ScheduleCipherPoolData createdPoolData = new ScheduleCipherPoolData(); + when(encryptionService.createInitialCipherPoolData(eq(data), any())).thenReturn(createdPoolData); + when(transactionService.storeInOwnTransaction(createdPoolData)).thenReturn(createdPoolData); + + /* execute */ + serviceToTest.startEncryptionRotation(data, "user1"); + + /* test */ + ArgumentCaptor entityCaptor = ArgumentCaptor.forClass(ScheduleCipherPoolData.class); + verify(transactionService).storeInOwnTransaction(entityCaptor.capture()); + + ScheduleCipherPoolData persistedPoolData = entityCaptor.getValue(); + assertThat(persistedPoolData.getCreatedFrom()).isEqualTo("user1"); // the stored entity contains the user information given to service + + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionServiceTest.java new file mode 100644 index 0000000000..d908c9e2ec --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionServiceTest.java @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; + +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.domain.schedule.ScheduleShutdownService; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubSecretKeyProviderFactory; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; + +class ScheduleEncryptionServiceTest { + + private ScheduleEncryptionService serviceToTest; + private SecHubSecretKeyProviderFactory secHubSecretKeyProviderFactory; + private EncryptionSupport encryptionSupport; + private PersistentCipherFactory cipherFactory; + private ScheduleEncryptionPoolFactory scheduleEncryptionPoolFactory; + private ScheduleEncryptionPool scheduleEncryptionPool; + private PersistentCipher fakedNoneCipher; + private ScheduleLatestCipherPoolDataCalculator scheduleLatestCipherPoolDataCalculator; + private ScheduleCipherPoolDataProvider poolDataProvider; + private PersistentCipher fakedAES256Cipher; + private DomainMessageService domainMessageService; + private SecHubOutdatedEncryptionPoolSupport outdatedEncryptionPoolSupport; + private ScheduleShutdownService shutdownService; + + @BeforeEach + void beforeEach() throws Exception { + serviceToTest = new ScheduleEncryptionService(); + + secHubSecretKeyProviderFactory = mock(SecHubSecretKeyProviderFactory.class); + when(secHubSecretKeyProviderFactory.createSecretKeyProvider(eq(PersistentCipherType.NONE), eq(SecHubCipherPasswordSourceType.NONE), any())) + .thenReturn(null); + + fakedNoneCipher = mock(PersistentCipher.class, "faked none cipher"); + fakedAES256Cipher = mock(PersistentCipher.class, "faked AES256 cipher"); + cipherFactory = mock(PersistentCipherFactory.class); + + when(cipherFactory.createCipher(null, PersistentCipherType.NONE)).thenReturn(fakedNoneCipher); + when(cipherFactory.createCipher(null, PersistentCipherType.AES_GCM_SIV_256)).thenReturn(fakedAES256Cipher); + + encryptionSupport = mock(EncryptionSupport.class); + + domainMessageService = mock(DomainMessageService.class); + + scheduleEncryptionPoolFactory = mock(ScheduleEncryptionPoolFactory.class); + scheduleEncryptionPool = mock(ScheduleEncryptionPool.class); + scheduleLatestCipherPoolDataCalculator = mock(ScheduleLatestCipherPoolDataCalculator.class); + poolDataProvider = mock(ScheduleCipherPoolDataProvider.class); + outdatedEncryptionPoolSupport = mock(SecHubOutdatedEncryptionPoolSupport.class); + shutdownService = mock(ScheduleShutdownService.class); + + serviceToTest.poolDataProvider = poolDataProvider; + serviceToTest.encryptionSupport = encryptionSupport; + serviceToTest.scheduleEncryptionPoolFactory = scheduleEncryptionPoolFactory; + serviceToTest.latestCipherPoolDataCalculator = scheduleLatestCipherPoolDataCalculator; + serviceToTest.secretKeyProviderFactory = secHubSecretKeyProviderFactory; + serviceToTest.cipherFactory = cipherFactory; + serviceToTest.domainMessageService = domainMessageService; + serviceToTest.outdatedEncryptionPoolSupport = outdatedEncryptionPoolSupport; + serviceToTest.shutdownService = shutdownService; + + when(scheduleEncryptionPoolFactory.createEncryptionPool(any())).thenReturn(scheduleEncryptionPool); + } + + @Test + void application_started_triggers_encryption_pool_refresh_event() throws Exception { + /* prepare */ + Long latestCipherPoolId = Long.valueOf(1); + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(anyList())).thenReturn(latestCipherPoolId); + when(scheduleEncryptionPool.getCipherForPoolId(latestCipherPoolId)).thenReturn(fakedNoneCipher); + + /* execute */ + serviceToTest.applicationStarted(); + + /* test */ + assertEncryptionPoolInitEventSent(); + + } + + @Test + void createInitialCipherPoolData_working_as_expected() throws Exception { + + /* prepare */ + String testText = "testtext"; + + SecHubEncryptionData data = new SecHubEncryptionData(); + data.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_256); + data.setPasswordSourceType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + data.setPasswordSourceData("SECRET_1"); + + byte[] initBytes = UUID.randomUUID().toString().getBytes(); + + EncryptionResult result = mock(EncryptionResult.class); + byte[] encryptedBytes = "faked-encryped-content".getBytes(Charset.forName("UTF-8")); + when(result.getEncryptedData()).thenReturn(encryptedBytes); + when(result.getInitialVector()).thenReturn(new InitializationVector(initBytes)); + + when(encryptionSupport.encryptString(eq(testText), eq(fakedAES256Cipher))).thenReturn(result); + when(encryptionSupport.decryptString(eq(encryptedBytes), eq(fakedAES256Cipher), eq(new InitializationVector(initBytes)))).thenReturn(testText); + + /* execute */ + ScheduleCipherPoolData createdPoolData = serviceToTest.createInitialCipherPoolData(data, testText); + + /* test */ + ArgumentCaptor testTextCaptor = ArgumentCaptor.forClass(String.class); + verify(encryptionSupport).encryptString(testTextCaptor.capture(), eq(fakedAES256Cipher)); + + String encryptedTestText = testTextCaptor.getValue(); + assertThat(encryptedTestText).isEqualTo(testText); + + assertThat(createdPoolData.getPasswordSourceType()).isEqualTo(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + assertThat(createdPoolData.getTestEncrypted()).isEqualTo(encryptedBytes); + assertThat(createdPoolData.getPasswordSourceData()).isEqualTo("SECRET_1"); + assertThat(createdPoolData.getTestInitialVector()).isEqualTo(initBytes); + assertThat(createdPoolData.getTestText()).isEqualTo(testText); + assertThat(createdPoolData.getAlgorithm()).isEqualTo(SecHubCipherAlgorithm.AES_GCM_SIV_256); + assertThat(createdPoolData.getCreatedFrom()).isNull();// the user is NOT set, as defined in JavaDoc of method + assertThat(createdPoolData.getCreated()).isNotNull(); + + } + + @Test + void createInitialCipherPoolData_null_from_encryption_support_throws_exception() throws Exception { + + /* prepare */ + String testText = "testtext"; + + SecHubEncryptionData data = new SecHubEncryptionData(); + data.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_256); + data.setPasswordSourceType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + data.setPasswordSourceData("SECRET_1"); + + byte[] initBytes = UUID.randomUUID().toString().getBytes(); + + EncryptionResult result = mock(EncryptionResult.class); + byte[] encryptedBytes = "faked-encryped-content".getBytes(Charset.forName("UTF-8")); + when(result.getEncryptedData()).thenReturn(encryptedBytes); + when(result.getInitialVector()).thenReturn(new InitializationVector(initBytes)); + + when(encryptionSupport.encryptString(eq(testText), eq(fakedAES256Cipher))).thenReturn(result); + when(encryptionSupport.decryptString(eq(encryptedBytes), eq(fakedAES256Cipher), eq(new InitializationVector(initBytes)))).thenReturn(null); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.createInitialCipherPoolData(data, testText)).isInstanceOf(ScheduleEncryptionException.class) + .hasMessageContaining("decrypted value is null"); + + } + + @Test + void createInitialCipherPoolData_decrypted_test_text_not_as_origin_throws_exception() throws Exception { + + /* prepare */ + String testText = "testtext"; + + SecHubEncryptionData data = new SecHubEncryptionData(); + data.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_256); + data.setPasswordSourceType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + data.setPasswordSourceData("SECRET_1"); + + byte[] initBytes = UUID.randomUUID().toString().getBytes(); + + EncryptionResult result = mock(EncryptionResult.class); + byte[] encryptedBytes = "faked-encryped-content".getBytes(Charset.forName("UTF-8")); + when(result.getEncryptedData()).thenReturn(encryptedBytes); + when(result.getInitialVector()).thenReturn(new InitializationVector(initBytes)); + + when(encryptionSupport.encryptString(eq(testText), eq(fakedAES256Cipher))).thenReturn(result); + when(encryptionSupport.decryptString(eq(encryptedBytes), eq(fakedAES256Cipher), eq(new InitializationVector(initBytes)))).thenReturn("wrong-result"); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.createInitialCipherPoolData(data, testText)).isInstanceOf(ScheduleEncryptionException.class) + .hasMessageContaining("decrypted value is not origin test text"); + + } + + @Test + void refresh_pooldata_provider_says_same_poolids_last_cipher_id_kept() throws Exception { + + /* prepare */ + when(scheduleEncryptionPool.getAllPoolIds()).thenReturn(Set.of(5L)); + + // simulate originally setup done on startup; + long latestCipherPoolId = 4L; + serviceToTest.scheduleEncryptionPool = scheduleEncryptionPool; + serviceToTest.latestCipherPoolId = latestCipherPoolId;// just other value; + + // important part: contains exactly .... + when(poolDataProvider.isContainingExactlyGivenPoolIds(any())).thenReturn(true); + + /* execute */ + serviceToTest.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + + /* test */ + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(latestCipherPoolId); // still same + assertNoDomainMessageEventSent(); + + } + + @Test + void refresh_pooldata_provider_says_same_poolids_no_new_pool_created_or_used() throws Exception { + + /* prepare */ + when(scheduleEncryptionPool.getAllPoolIds()).thenReturn(Set.of(5L)); + + // simulate originally setup done on startup; + long latestCipherPoolId = 4L; + serviceToTest.scheduleEncryptionPool = scheduleEncryptionPool; + serviceToTest.latestCipherPoolId = latestCipherPoolId;// just other value; + + // important part: contains exactly .... + when(poolDataProvider.isContainingExactlyGivenPoolIds(any())).thenReturn(true); + + /* execute */ + serviceToTest.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + + /* test */ + verify(scheduleEncryptionPoolFactory, never()).createEncryptionPool(any()); // never a new pool is created! + verify(scheduleEncryptionPool, never()).getCipherForPoolId(anyLong()); + assertThat(serviceToTest.scheduleEncryptionPool).isSameAs(scheduleEncryptionPool);// not changed + assertNoDomainMessageEventSent(); + + } + + @Test + void refresh_when_pool_ids_are_NOT_same_latest_cipher_pool_id_is_changed() throws Exception { + + /* prepare */ + ScheduleCipherPoolData poolData1 = mock(ScheduleCipherPoolData.class); + when(poolData1.getId()).thenReturn(Long.valueOf(4)); + List poolDataList = List.of(poolData1); + + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(poolDataList); + long oldCipherPoolId = 4L; + when(scheduleEncryptionPoolFactory.createEncryptionPool(poolDataList)).thenReturn(scheduleEncryptionPool); + + long newCipherPoolId = 5L; + when(scheduleEncryptionPool.getAllPoolIds()).thenReturn(Set.of(oldCipherPoolId, newCipherPoolId)); + when(scheduleEncryptionPool.getCipherForPoolId(newCipherPoolId)).thenReturn(fakedNoneCipher); + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(poolDataList)).thenReturn(newCipherPoolId); + + // simulate originally setup done on startup; + serviceToTest.scheduleEncryptionPool = scheduleEncryptionPool; + serviceToTest.latestCipherPoolId = oldCipherPoolId; + + // important part: contains exactly .... + when(poolDataProvider.isContainingExactlyGivenPoolIds(any())).thenReturn(false); + + /* check preconditions */ + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(oldCipherPoolId); + + /* execute */ + serviceToTest.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + + /* test */ + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(newCipherPoolId); + + } + + @Test + void refresh_when_pool_ids_have_changed_a_new_pool_is_created_and_used() throws Exception { + + /* prepare */ + ScheduleCipherPoolData poolData1 = mock(ScheduleCipherPoolData.class); + when(poolData1.getId()).thenReturn(Long.valueOf(4)); + List poolDataList = List.of(poolData1); + + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(poolDataList); + long oldCipherPoolId = 4L; + + long newCipherPoolId = 5L; + when(scheduleEncryptionPool.getAllPoolIds()).thenReturn(Set.of(oldCipherPoolId, newCipherPoolId)); + + ScheduleEncryptionPool newEncryptionPool = mock(ScheduleEncryptionPool.class); + when(scheduleEncryptionPoolFactory.createEncryptionPool(poolDataList)).thenReturn(newEncryptionPool); + when(newEncryptionPool.getCipherForPoolId(newCipherPoolId)).thenReturn(fakedNoneCipher); + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(poolDataList)).thenReturn(newCipherPoolId); + + // simulate originally setup done on startup; + serviceToTest.scheduleEncryptionPool = scheduleEncryptionPool; + serviceToTest.latestCipherPoolId = oldCipherPoolId; + + // important part: contains exactly .... + when(poolDataProvider.isContainingExactlyGivenPoolIds(any())).thenReturn(false); + + /* check preconditions */ + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(oldCipherPoolId); + + /* execute */ + serviceToTest.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + + /* test */ + verify(scheduleEncryptionPoolFactory).createEncryptionPool(poolDataList); // a new pool is created! + assertThat(serviceToTest.scheduleEncryptionPool).isEqualTo(newEncryptionPool); // and set + assertEncryptionPoolInitEventSent(); // event must be sent! + + } + + @Test + void refresh_when_pool_ids_have_changed_but_new_pool_creation_fails_but_not_outdated_old_setup_is_kept() throws Exception { + + /* prepare */ + ScheduleCipherPoolData poolData1 = mock(ScheduleCipherPoolData.class); + when(poolData1.getId()).thenReturn(Long.valueOf(4)); + List poolDataList = List.of(poolData1); + + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(poolDataList); + long oldCipherPoolId = 4L; + + long newCipherPoolId = 5L; + when(scheduleEncryptionPool.getAllPoolIds()).thenReturn(Set.of(oldCipherPoolId, newCipherPoolId)); + + when(scheduleEncryptionPoolFactory.createEncryptionPool(poolDataList)) + .thenThrow(new ScheduleEncryptionException("was not able to create new encryption pool")); + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(poolDataList)).thenReturn(newCipherPoolId); + + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionStillAllowedOnThisClusterMember()).thenReturn(true); + // simulate originally setup done on startup; + serviceToTest.scheduleEncryptionPool = scheduleEncryptionPool; + serviceToTest.latestCipherPoolId = oldCipherPoolId; + + // important part: contains exactly .... + when(poolDataProvider.isContainingExactlyGivenPoolIds(any())).thenReturn(false); + + /* check preconditions */ + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(oldCipherPoolId); + + /* execute */ + serviceToTest.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + + /* test */ + verify(scheduleEncryptionPoolFactory).createEncryptionPool(poolDataList); // it is tried to create a new pool + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(oldCipherPoolId); // not changed + assertThat(serviceToTest.scheduleEncryptionPool).isEqualTo(scheduleEncryptionPool); // not changed + + assertNoDomainMessageEventSent(); // no event may be sent! + + verify(shutdownService, never()).shutdownApplication(); // no shutdown triggered + + } + + @Test + void refresh_when_pool_ids_have_changed_but_new_pool_creation_fails_but_too_long_outdated_shutdown_triggered() throws Exception { + + /* prepare */ + ScheduleCipherPoolData poolData1 = mock(ScheduleCipherPoolData.class); + when(poolData1.getId()).thenReturn(Long.valueOf(4)); + List poolDataList = List.of(poolData1); + + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(poolDataList); + long oldCipherPoolId = 4L; + + long newCipherPoolId = 5L; + when(scheduleEncryptionPool.getAllPoolIds()).thenReturn(Set.of(oldCipherPoolId, newCipherPoolId)); + + when(scheduleEncryptionPoolFactory.createEncryptionPool(poolDataList)) + .thenThrow(new ScheduleEncryptionException("was not able to create new encryption pool")); + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(poolDataList)).thenReturn(newCipherPoolId); + when(outdatedEncryptionPoolSupport.isOutdatedEncryptionStillAllowedOnThisClusterMember()).thenReturn(false); + + // simulate originally setup done on startup; + serviceToTest.scheduleEncryptionPool = scheduleEncryptionPool; + serviceToTest.latestCipherPoolId = oldCipherPoolId; + + // important part: contains exactly .... + when(poolDataProvider.isContainingExactlyGivenPoolIds(any())).thenReturn(false); + + /* check preconditions */ + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(oldCipherPoolId); + + /* execute */ + serviceToTest.refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + + /* test */ + verify(scheduleEncryptionPoolFactory).createEncryptionPool(poolDataList); // it is tried to create a new pool + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(oldCipherPoolId); // not changed + assertThat(serviceToTest.scheduleEncryptionPool).isEqualTo(scheduleEncryptionPool); // not changed + + assertNoDomainMessageEventSent(); // no event may be sent! + verify(shutdownService).shutdownApplication(); + + } + + @ParameterizedTest + @ValueSource(longs = { 0, 1, 3270 }) + void applicationStarted_latest_cipher_pool_id_is_resolved(long value) throws Exception { + + /* prepare */ + Long latestCipherPoolId = Long.valueOf(value); + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(anyList())).thenReturn(latestCipherPoolId); + when(scheduleEncryptionPool.getCipherForPoolId(latestCipherPoolId)).thenReturn(fakedNoneCipher); + + /* execute */ + serviceToTest.applicationStarted(); + + /* test */ + assertThat(serviceToTest.latestCipherPoolId).isEqualTo(latestCipherPoolId); + + } + + @ParameterizedTest + @ValueSource(longs = { 0, 1, 3270 }) + void applicationStarted_encryption_pool_has_not_latest_pool_id_inside_throws_illegal_state(long value) { + /* prepare */ + Long latestPoolId = Long.valueOf(value); + when(scheduleEncryptionPool.getCipherForPoolId(latestPoolId)).thenReturn(null); + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(anyList())).thenReturn(latestPoolId); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.applicationStarted()).isInstanceOf(IllegalStateException.class) + .hasMessage("Encryption pool has no entry for latest cipher pool id: %d", latestPoolId); + + } + + @Test + void applicationStarted_encryption_pool_is_created_by_factory_with_data_from_pooldataprovider() throws Exception { + /* prepare */ + Long latestCipherPoolId = Long.valueOf(12); + List list = new ArrayList<>(); + + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(anyList())).thenReturn(latestCipherPoolId); + when(scheduleEncryptionPool.getCipherForPoolId(latestCipherPoolId)).thenReturn(fakedNoneCipher); + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(list); + when(scheduleEncryptionPoolFactory.createEncryptionPool(list)).thenReturn(scheduleEncryptionPool); + + /* execute + test */ + serviceToTest.applicationStarted(); + + verify(poolDataProvider).ensurePoolDataAvailable(); + verify(scheduleEncryptionPoolFactory).createEncryptionPool(list); + verify(scheduleEncryptionPool).getCipherForPoolId(latestCipherPoolId); // the created pool is used + } + + @Test + void encryptWithLatestCipher_calls_encryptionsupport_with_latest_cipher_and_uses_result() throws Exception { + /* prepare */ + String stringToEncrypt = "please-encrypt-me"; + Long latestCipherPoolId = Long.valueOf(12); + + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(anyList())).thenReturn(latestCipherPoolId); + when(scheduleEncryptionPool.getCipherForPoolId(latestCipherPoolId)).thenReturn(fakedNoneCipher); + + // fake encryption by cipher + EncryptionResult encryptionResultFromSupport = mock(EncryptionResult.class); + byte[] encryptedData = "i-am-encrypted".getBytes(); + InitializationVector initialVector = mock(InitializationVector.class); + when(encryptionResultFromSupport.getEncryptedData()).thenReturn(encryptedData); + when(encryptionResultFromSupport.getInitialVector()).thenReturn(initialVector); + + when(encryptionSupport.encryptString(stringToEncrypt, fakedNoneCipher)).thenReturn(encryptionResultFromSupport); + + // simulate spring startup - necessary to get pool filled. + serviceToTest.applicationStarted(); + + /* execute */ + ScheduleEncryptionResult result = serviceToTest.encryptWithLatestCipher(stringToEncrypt); + + /* test */ + verify(encryptionSupport).encryptString(stringToEncrypt, fakedNoneCipher); + + assertThat(result.getCipherPoolId()).isEqualTo(latestCipherPoolId); + assertThat(result.getEncryptedData()).isEqualTo(encryptedData); + assertThat(result.getInitialVector()).isEqualTo(initialVector); + + } + + @Test + void decrypt_with_latest_cipher_for_decryption() throws Exception { + /* prepare */ + byte[] encryptedData = "i-am-encrypted".getBytes(); + String decryptedString = "please-encrypt-me"; + Long latestCipherPoolId = Long.valueOf(12); + Long usedCipherPoolId = latestCipherPoolId; + PersistentCipher cipherForDecryption = fakedNoneCipher; + + // necessary to get startup not failing for missing latest pool id: + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(anyList())).thenReturn(latestCipherPoolId); + when(scheduleEncryptionPool.getCipherForPoolId(latestCipherPoolId)).thenReturn(fakedNoneCipher); + + // fake encryption by cipher + EncryptionResult encryptionResultFromSupport = mock(EncryptionResult.class); + InitializationVector initialVector = mock(InitializationVector.class); + when(encryptionResultFromSupport.getEncryptedData()).thenReturn(encryptedData); + when(encryptionResultFromSupport.getInitialVector()).thenReturn(initialVector); + + when(encryptionSupport.decryptString(encryptedData, cipherForDecryption, initialVector)).thenReturn(decryptedString); + + // simulate spring startup - necessary to get pool filled. + serviceToTest.applicationStarted(); + + /* execute */ + String decrypted = serviceToTest.decryptToString(encryptedData, usedCipherPoolId, initialVector); + + /* test */ + verify(encryptionSupport).decryptString(encryptedData, cipherForDecryption, initialVector); + assertThat(decrypted).isEqualTo(decryptedString); + + } + + @Test + void decrypt_with_other_cipher_for_decryption() throws Exception { + /* prepare */ + byte[] encryptedData = "i-am-encrypted".getBytes(); + String decryptedString = "please-encrypt-me"; + Long latestCipherPoolId = Long.valueOf(12); + Long usedCipherPoolId = Long.valueOf(1); + PersistentCipher cipherForDecryption = mock(PersistentCipher.class, "cipher for decryption"); + + // necessary to get startup not failing for missing latest pool id: + when(scheduleLatestCipherPoolDataCalculator.calculateLatestPoolId(anyList())).thenReturn(latestCipherPoolId); + when(scheduleEncryptionPool.getCipherForPoolId(latestCipherPoolId)).thenReturn(fakedNoneCipher); + + // but we use here an "older" cipher pool entry + when(scheduleEncryptionPool.getCipherForPoolId(usedCipherPoolId)).thenReturn(cipherForDecryption); + + // fake encryption by cipher + EncryptionResult encryptionResultFromSupport = mock(EncryptionResult.class); + InitializationVector initialVector = mock(InitializationVector.class); + when(encryptionResultFromSupport.getEncryptedData()).thenReturn(encryptedData); + when(encryptionResultFromSupport.getInitialVector()).thenReturn(initialVector); + + when(encryptionSupport.decryptString(encryptedData, cipherForDecryption, initialVector)).thenReturn(decryptedString); + + // simulate spring startup - necessary to get pool filled. + serviceToTest.applicationStarted(); + + /* execute */ + String decrypted = serviceToTest.decryptToString(encryptedData, usedCipherPoolId, initialVector); + + /* test */ + verify(encryptionSupport).decryptString(encryptedData, cipherForDecryption, initialVector); + assertThat(decrypted).isEqualTo(decryptedString); + + } + + void assertNoDomainMessageEventSent() { + verify(domainMessageService, never()).sendAsynchron(any(DomainMessage.class)); + } + + void assertEncryptionPoolInitEventSent() { + ArgumentCaptor captor = ArgumentCaptor.forClass(DomainMessage.class); + verify(domainMessageService).sendAsynchron(captor.capture()); + + DomainMessage sentDomainMessage = captor.getValue(); + assertThat(sentDomainMessage.getMessageId()).isEqualTo(MessageID.SCHEDULE_ENCRYPTION_POOL_INITIALIZED); + } +} \ No newline at end of file diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionStatusServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionStatusServiceTest.java new file mode 100644 index 0000000000..7c6639bd61 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleEncryptionStatusServiceTest.java @@ -0,0 +1,186 @@ +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; + +import java.time.LocalDateTime; +import java.util.Iterator; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.model.job.ExecutionState; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherAlgorithm; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubCipherPasswordSourceType; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionData; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionStatus; + +class ScheduleEncryptionStatusServiceTest { + + private ScheduleEncryptionStatusService serviceToTest; + private ScheduleCipherPoolDataRepository poolDataRepository; + private SecHubJobRepository jobRepository; + + @BeforeEach + void beforeEach() { + + poolDataRepository = mock(ScheduleCipherPoolDataRepository.class); + jobRepository = mock(SecHubJobRepository.class); + + serviceToTest = new ScheduleEncryptionStatusService(); + serviceToTest.poolDataRepository = poolDataRepository; + serviceToTest.jobRepository = jobRepository; + } + + @Test + void createEncryptionStatus__no_pool_data() { + /* prepare */ + when(poolDataRepository.findAll()).thenReturn(List.of()); + + /* execute */ + SecHubDomainEncryptionStatus status = serviceToTest.createEncryptionStatus(); + + /* test */ + assertThat(status).isNotNull(); + assertThat(status.getData()).isEmpty(); + + } + + @Test + void createEncryptionStatus__one_pool_data() { + + /* prepare */ + ScheduleCipherPoolData poolData1 = mock(ScheduleCipherPoolData.class); + LocalDateTime creationTime = LocalDateTime.now(); + String user = "user1"; + String passwordSourceData = "the-data"; + SecHubCipherPasswordSourceType passwordSourceType = SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE; + long poolid = 33L; + + long count1Initializing = 1L; + long count1Canceled = 20L; + long count1Ended = 1023L; + + when(poolData1.getId()).thenReturn(poolid); + when(poolData1.getAlgorithm()).thenReturn(SecHubCipherAlgorithm.AES_GCM_SIV_128); + when(poolData1.getCreated()).thenReturn(creationTime); + when(poolData1.getCreatedFrom()).thenReturn(user); + when(poolData1.getPasswordSourceData()).thenReturn(passwordSourceData); + when(poolData1.getPasswordSourceType()).thenReturn(passwordSourceType); + + when(poolDataRepository.findAll()).thenReturn(List.of(poolData1)); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.INITIALIZING, poolid)).thenReturn(count1Initializing); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.CANCELED, poolid)).thenReturn(count1Canceled); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.ENDED, poolid)).thenReturn(count1Ended); + + /* execute */ + SecHubDomainEncryptionStatus status = serviceToTest.createEncryptionStatus(); + + /* test */ + assertThat(status).isNotNull(); + assertThat(status.getData()).isNotEmpty().hasSize(1); + + SecHubDomainEncryptionData data1 = status.getData().iterator().next(); + assertThat(data1.getId()).isEqualTo(String.valueOf(poolid)); + assertThat(data1.getCreated()).isEqualTo(creationTime); + assertThat(data1.getCreatedFrom()).isEqualTo(user); + assertThat(data1.getPasswordSource().getType()).isEqualTo(passwordSourceType); + assertThat(data1.getPasswordSource().getData()).isEqualTo(passwordSourceData); + + assertThat(data1.getUsage()).containsEntry("job.state.canceled", count1Canceled); + assertThat(data1.getUsage()).containsEntry("job.state.initializing", count1Initializing); + assertThat(data1.getUsage()).containsEntry("job.state.ended", count1Ended); + + } + + @Test + void createEncryptionStatus__two_pool_data() { + + /* prepare */ + ScheduleCipherPoolData poolData0 = mock(ScheduleCipherPoolData.class); + LocalDateTime creationTime0 = LocalDateTime.now(); + String user0 = "user0"; + String passwordSourceData0 = "the-data0"; + SecHubCipherPasswordSourceType passwordSourceType0 = SecHubCipherPasswordSourceType.NONE; + long poolid0 = 12; + + long count0Initializing = 10L; + long count0Canceled = 30L; + long count0Ended = 2023L; + long count0CancelRequest = 3; + + when(poolData0.getId()).thenReturn(poolid0); + when(poolData0.getAlgorithm()).thenReturn(SecHubCipherAlgorithm.AES_GCM_SIV_128); + when(poolData0.getCreated()).thenReturn(creationTime0); + when(poolData0.getCreatedFrom()).thenReturn(user0); + when(poolData0.getPasswordSourceData()).thenReturn(passwordSourceData0); + when(poolData0.getPasswordSourceType()).thenReturn(passwordSourceType0); + + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.INITIALIZING, poolid0)).thenReturn(count0Initializing); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.CANCELED, poolid0)).thenReturn(count0Canceled); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.ENDED, poolid0)).thenReturn(count0Ended); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.CANCEL_REQUESTED, poolid0)).thenReturn(count0CancelRequest); + + ScheduleCipherPoolData poolData1 = mock(ScheduleCipherPoolData.class); + LocalDateTime creationTime1 = LocalDateTime.now(); + String user = "user1"; + String passwordSourceData1 = "the-data"; + SecHubCipherPasswordSourceType passwordSourceType1 = SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE; + long poolid1 = 33L; + + long count1Initializing = 1L; + long count1Canceled = 20L; + long count1Ended = 1023L; + + when(poolData1.getId()).thenReturn(poolid1); + when(poolData1.getAlgorithm()).thenReturn(SecHubCipherAlgorithm.AES_GCM_SIV_128); + when(poolData1.getCreated()).thenReturn(creationTime1); + when(poolData1.getCreatedFrom()).thenReturn(user); + when(poolData1.getPasswordSourceData()).thenReturn(passwordSourceData1); + when(poolData1.getPasswordSourceType()).thenReturn(passwordSourceType1); + + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.INITIALIZING, poolid1)).thenReturn(count1Initializing); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.CANCELED, poolid1)).thenReturn(count1Canceled); + when(jobRepository.countJobsInExecutionStateAndEncryptedWithPoolId(ExecutionState.ENDED, poolid1)).thenReturn(count1Ended); + + when(poolDataRepository.findAll()).thenReturn(List.of(poolData0, poolData1)); + + /* execute */ + SecHubDomainEncryptionStatus status = serviceToTest.createEncryptionStatus(); + + /* test */ + assertThat(status).isNotNull(); + assertThat(status.getData()).isNotEmpty().hasSize(2); + + Iterator iterator = status.getData().iterator(); + SecHubDomainEncryptionData data0 = iterator.next(); + assertThat(data0.getId()).isEqualTo(String.valueOf(poolid0)); + assertThat(data0.getCreated()).isEqualTo(creationTime0); + assertThat(data0.getCreatedFrom()).isEqualTo(user0); + assertThat(data0.getPasswordSource().getType()).isEqualTo(passwordSourceType0); + assertThat(data0.getPasswordSource().getData()).isEqualTo(passwordSourceData0); + + assertThat(data0.getUsage()).containsEntry("job.state.canceled", count0Canceled); + assertThat(data0.getUsage()).containsEntry("job.state.initializing", count0Initializing); + assertThat(data0.getUsage()).containsEntry("job.state.ended", count0Ended); + assertThat(data0.getUsage()).containsEntry("job.state.cancel_requested", count0CancelRequest); + + SecHubDomainEncryptionData data1 = iterator.next(); + assertThat(data1.getId()).isEqualTo(String.valueOf(poolid1)); + assertThat(data1.getCreated()).isEqualTo(creationTime1); + assertThat(data1.getCreatedFrom()).isEqualTo(user); + assertThat(data1.getPasswordSource().getType()).isEqualTo(passwordSourceType1); + assertThat(data1.getPasswordSource().getData()).isEqualTo(passwordSourceData1); + + assertThat(data1.getUsage()).containsEntry("job.state.canceled", count1Canceled); + assertThat(data1.getUsage()).containsEntry("job.state.initializing", count1Initializing); + assertThat(data1.getUsage()).containsEntry("job.state.ended", count1Ended); + + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleLatestCipherPoolDataCalculatorTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleLatestCipherPoolDataCalculatorTest.java new file mode 100644 index 0000000000..a28b5a754b --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleLatestCipherPoolDataCalculatorTest.java @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +class ScheduleLatestCipherPoolDataCalculatorTest { + + @Test + void null_list_results_in_latest_pool_id_null() { + + /* prepare */ + ScheduleLatestCipherPoolDataCalculator calculatorToTest = new ScheduleLatestCipherPoolDataCalculator(); + + List entries = null; + + /* execute */ + Long result = calculatorToTest.calculateLatestPoolId(entries); + + /* test */ + assertThat(result).isNull(); + } + + @Test + void null_list_results_in_latest_pool_id_null_object_variant() { + + /* prepare */ + ScheduleLatestCipherPoolDataCalculator calculatorToTest = new ScheduleLatestCipherPoolDataCalculator(); + + List entries = null; + + /* execute */ + ScheduleCipherPoolData result = calculatorToTest.calculateLatestPoolData(entries); + + /* test */ + assertThat(result).isNull(); + } + + @Test + void empty_list_results_in_latest_pool_id_null() { + + /* prepare */ + ScheduleLatestCipherPoolDataCalculator calculatorToTest = new ScheduleLatestCipherPoolDataCalculator(); + + List entries = new ArrayList<>(); + + /* execute */ + Long result = calculatorToTest.calculateLatestPoolId(entries); + + /* test */ + assertThat(result).isNull(); + } + + @Test + void empty_list_results_in_latest_pool_id_null_object_variant() { + + /* prepare */ + ScheduleLatestCipherPoolDataCalculator calculatorToTest = new ScheduleLatestCipherPoolDataCalculator(); + + List entries = new ArrayList<>(); + + /* execute */ + ScheduleCipherPoolData result = calculatorToTest.calculateLatestPoolData(entries); + + /* test */ + assertThat(result).isNull(); + } + + @Test + void two_entries_newer_one_resolved() { + + /* prepare */ + ScheduleLatestCipherPoolDataCalculator calculatorToTest = new ScheduleLatestCipherPoolDataCalculator(); + + // create valid cipher pool data + ScheduleCipherPoolData data1 = new ScheduleCipherPoolData(); + data1.id = Long.valueOf(0); + data1.created = LocalDateTime.now().minusDays(1); + + ScheduleCipherPoolData data2 = new ScheduleCipherPoolData(); + data2.id = Long.valueOf(1); + data2.created = LocalDateTime.now(); + + List entries = new ArrayList<>(); + entries.add(data1); + entries.add(data2); + + /* execute */ + Long result = calculatorToTest.calculateLatestPoolId(entries); + + /* test */ + assertThat(result).describedAs("data2 is the newer one").isEqualTo(data2.id); + } + + @Test + void three_entries_newer_resolved_no_matter_that_inside_middle() { + + /* prepare */ + ScheduleLatestCipherPoolDataCalculator calculatorToTest = new ScheduleLatestCipherPoolDataCalculator(); + + // create valid cipher pool data + ScheduleCipherPoolData data1 = new ScheduleCipherPoolData(); + data1.id = Long.valueOf(0); + data1.created = LocalDateTime.now().minusDays(2); + + ScheduleCipherPoolData data2 = new ScheduleCipherPoolData(); + data2.id = Long.valueOf(1); + data2.created = LocalDateTime.now().minusDays(1); + + ScheduleCipherPoolData data3 = new ScheduleCipherPoolData(); + data3.id = Long.valueOf(2); + data3.created = LocalDateTime.now(); + + List entries = new ArrayList<>(); + entries.add(data1); + entries.add(data3); + entries.add(data2); + + /* execute */ + Long result = calculatorToTest.calculateLatestPoolId(entries); + + /* test */ + assertThat(result).describedAs("data3 is the newer one - even when added in middle of list").isEqualTo(data3.id); + } + + @Test + void three_entries_newer_resolved_no_matter_that_inside_middle_object_variant() { + + /* prepare */ + ScheduleLatestCipherPoolDataCalculator calculatorToTest = new ScheduleLatestCipherPoolDataCalculator(); + + // create valid cipher pool data + ScheduleCipherPoolData data1 = new ScheduleCipherPoolData(); + data1.id = Long.valueOf(0); + data1.created = LocalDateTime.now().minusDays(2); + + ScheduleCipherPoolData data2 = new ScheduleCipherPoolData(); + data2.id = Long.valueOf(1); + data2.created = LocalDateTime.now().minusDays(1); + + ScheduleCipherPoolData data3 = new ScheduleCipherPoolData(); + data3.id = Long.valueOf(2); + data3.created = LocalDateTime.now(); + + List entries = new ArrayList<>(); + entries.add(data1); + entries.add(data3); + entries.add(data2); + + /* execute */ + ScheduleCipherPoolData result = calculatorToTest.calculateLatestPoolData(entries); + + /* test */ + assertThat(result).describedAs("data3 is the newer one - even when added in middle of list").isEqualTo(data3); + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleRefreshEncryptionServiceSetupTriggerServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleRefreshEncryptionServiceSetupTriggerServiceTest.java new file mode 100644 index 0000000000..90720d95d4 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/ScheduleRefreshEncryptionServiceSetupTriggerServiceTest.java @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; + +class ScheduleRefreshEncryptionServiceSetupTriggerServiceTest { + + private ScheduleRefreshEncryptionServiceSetupTriggerService serviceToTest; + private ScheduleEncryptionService encryptionService; + + @Test + void triggers_refresh_on_encryption_service() { + /* prepare */ + encryptionService = mock(ScheduleEncryptionService.class); + + serviceToTest = new ScheduleRefreshEncryptionServiceSetupTriggerService(); + serviceToTest.encryptionService = encryptionService; + + /* execute */ + serviceToTest.triggerEncryptionSetupRefresh(); + + /* test */ + verify(encryptionService).refreshEncryptionPoolAndLatestPoolIdIfNecessary(); + } + +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/SecHubOutdatedEncryptionPoolSupportTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/SecHubOutdatedEncryptionPoolSupportTest.java new file mode 100644 index 0000000000..d4ffe93502 --- /dev/null +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/encryption/SecHubOutdatedEncryptionPoolSupportTest.java @@ -0,0 +1,370 @@ +package com.mercedesbenz.sechub.domain.schedule.encryption; + +import static java.time.LocalDateTime.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import com.mercedesbenz.sechub.sharedkernel.SystemTimeProvider; + +class SecHubOutdatedEncryptionPoolSupportTest { + + private SecHubOutdatedEncryptionPoolSupport supportToTest; + private SystemTimeProvider systemtimeProvider; + private ScheduleCipherPoolDataProvider poolDataProvider; + private ScheduleLatestCipherPoolDataCalculator latestCipherPoolDataCalculator; + + @BeforeEach + void beforeEach() { + systemtimeProvider = mock(SystemTimeProvider.class); + poolDataProvider = mock(ScheduleCipherPoolDataProvider.class); + latestCipherPoolDataCalculator = mock(ScheduleLatestCipherPoolDataCalculator.class); + + supportToTest = new SecHubOutdatedEncryptionPoolSupport(); + supportToTest.systemtimeProvider = systemtimeProvider; + supportToTest.poolDataProvider = poolDataProvider; + supportToTest.latestCipherPoolDataCalculator = latestCipherPoolDataCalculator; + } + + @ParameterizedTest + @ArgumentsSource(OutdatedEncryptionPoolOnThisClusterMemberAllowedArgumentProvider.class) + @ArgumentsSource(OutdatedEncryptionPoolOnThisClusterMemberNotAllowedArgumentProvider.class) + void outdated_encryption_on_cluster_member_NOT_allowed_when_no_latest_pooldata(LocalDateTime now, long outdatedAcceptedMillis, + LocalDateTime latestPoolIdCreationTimestamp) throws Exception { + + /* prepare */ + when(systemtimeProvider.getNow()).thenReturn(now); + + List list = List.of(); // empty list... + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(null); + + supportToTest.acceptOutdatedEncryptionPoolInMilliseconds = outdatedAcceptedMillis; + + /* execute */ + boolean result = supportToTest.isOutdatedEncryptionStillAllowedOnThisClusterMember(); + + /* test */ + assertThat(result).isFalse(); + + } + + @ParameterizedTest + @ArgumentsSource(OutdatedEncryptionPoolOnThisClusterMemberNotAllowedArgumentProvider.class) + void outdated_encryption_on_cluster_member_NOT_allowed(LocalDateTime now, long outdatedAcceptedMillis, + LocalDateTime latestPoolIdCreationTimestamp) throws Exception { + + /* prepare */ + when(systemtimeProvider.getNow()).thenReturn(now); + + ScheduleCipherPoolData latestPoolData = mock(ScheduleCipherPoolData.class); + when(latestPoolData.getCreated()).thenReturn(latestPoolIdCreationTimestamp); + List list = List.of(latestPoolData); + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(latestPoolData); + + supportToTest.acceptOutdatedEncryptionPoolInMilliseconds = outdatedAcceptedMillis; + + /* execute */ + boolean result = supportToTest.isOutdatedEncryptionStillAllowedOnThisClusterMember(); + + /* test */ + assertThat(result).isFalse(); + + } + + @ParameterizedTest + @ArgumentsSource(OutdatedEncryptionPoolOnThisClusterMemberAllowedArgumentProvider.class) + void outdated_encryption_on_cluster_member_allowed(LocalDateTime now, long outdatedAcceptedMillis, + LocalDateTime latestPoolIdCreationTimestamp) throws Exception { + + /* prepare */ + when(systemtimeProvider.getNow()).thenReturn(now); + + ScheduleCipherPoolData latestPoolData = mock(ScheduleCipherPoolData.class); + when(latestPoolData.getCreated()).thenReturn(latestPoolIdCreationTimestamp); + List list = List.of(latestPoolData); + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(latestPoolData); + + supportToTest.acceptOutdatedEncryptionPoolInMilliseconds = outdatedAcceptedMillis; + + /* execute */ + boolean result = supportToTest.isOutdatedEncryptionStillAllowedOnThisClusterMember(); + + /* test */ + assertThat(result).isTrue(); + + } + + @ParameterizedTest + @ArgumentsSource(OutdatedEncryptionPoolInClusterPossibleArgumentProvider.class) + @ArgumentsSource(OutdatedEncryptionPoolInClusterNotPossibleArgumentProvider.class) + void outdated_encryption_pool_in_cluster_NOT_possible_when_no_latest_pooldata(LocalDateTime now, long outdatedAcceptedMillis, + LocalDateTime latestPoolIdCreationTimestamp, long refreshIntervalMillis, long refreshInitialDelayMillis) throws Exception { + + /* prepare */ + when(systemtimeProvider.getNow()).thenReturn(now); + + List list = List.of(); // empty list... + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(null); + + supportToTest.acceptOutdatedEncryptionPoolInMilliseconds = outdatedAcceptedMillis; + supportToTest.encryptionRefreshFixedDelayInMilliseconds = refreshIntervalMillis; + supportToTest.encryptionRefreshInitialDelayInMilliseconds = refreshInitialDelayMillis; + + /* execute */ + boolean result = supportToTest.isOutdatedEncryptionPoolPossibleInCluster(); + + /* test */ + assertThat(result).isFalse(); + + } + + @ParameterizedTest + @ArgumentsSource(OutdatedEncryptionPoolInClusterNotPossibleArgumentProvider.class) + void outdated_encryption_pool_in_cluster__NOT_possible(LocalDateTime now, long outdatedAcceptedMillis, + LocalDateTime latestPoolIdCreationTimestamp, long refreshIntervalMillis, long refreshInitialDelayMillis) throws Exception { + + /* prepare */ + when(systemtimeProvider.getNow()).thenReturn(now); + + ScheduleCipherPoolData latestPoolData = mock(ScheduleCipherPoolData.class); + when(latestPoolData.getCreated()).thenReturn(latestPoolIdCreationTimestamp); + List list = List.of(latestPoolData); + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(latestPoolData); + + supportToTest.acceptOutdatedEncryptionPoolInMilliseconds = outdatedAcceptedMillis; + supportToTest.encryptionRefreshFixedDelayInMilliseconds = refreshIntervalMillis; + supportToTest.encryptionRefreshInitialDelayInMilliseconds = refreshInitialDelayMillis; + + /* execute */ + boolean result = supportToTest.isOutdatedEncryptionPoolPossibleInCluster(); + + /* test */ + assertThat(result).isFalse(); + + } + + @ParameterizedTest + @ArgumentsSource(OutdatedEncryptionPoolInClusterPossibleArgumentProvider.class) + void outdated_encryption_pool_in_cluster__possible(LocalDateTime now, long outdatedAcceptedMillis, + LocalDateTime latestPoolIdCreationTimestamp, long refreshIntervalMillis, long refreshInitialDelayMillis) throws Exception { + + /* prepare */ + when(systemtimeProvider.getNow()).thenReturn(now); + + ScheduleCipherPoolData latestPoolData = mock(ScheduleCipherPoolData.class); + when(latestPoolData.getCreated()).thenReturn(latestPoolIdCreationTimestamp); + List list = List.of(latestPoolData); + when(poolDataProvider.ensurePoolDataAvailable()).thenReturn(list); + when(latestCipherPoolDataCalculator.calculateLatestPoolData(list)).thenReturn(latestPoolData); + + supportToTest.acceptOutdatedEncryptionPoolInMilliseconds = outdatedAcceptedMillis; + supportToTest.encryptionRefreshFixedDelayInMilliseconds = refreshIntervalMillis; + supportToTest.encryptionRefreshInitialDelayInMilliseconds = refreshInitialDelayMillis; + + /* execute */ + boolean result = supportToTest.isOutdatedEncryptionPoolPossibleInCluster(); + + /* test */ + assertThat(result).isTrue(); + + } + + static class OutdatedEncryptionPoolOnThisClusterMemberAllowedArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + /* @formatter:off */ + return Stream.of( + /*create pool entry now + * |------120s----------------------| + * | | + * |------------------------ accept outdated >120s--->X + */ + Arguments.of(now(), acceptOutdated(121), now().minusSeconds(120)), + Arguments.of(now(), acceptOutdated(210), now().minusSeconds(120)), + Arguments.of(now(), acceptOutdated(10000), now().minusSeconds(120)) + ); + /* @formatter:on */ + + } + + } + + static class OutdatedEncryptionPoolOnThisClusterMemberNotAllowedArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + /* @formatter:off */ + return Stream.of( + /*create pool entry now + * |------120s----------------------| + * | | + * |----- accept outdated < 120s->X + */ + Arguments.of(now(), acceptOutdated(119), now().minusSeconds(120)), + Arguments.of(now(), acceptOutdated(21), now().minusSeconds(120)), + Arguments.of(now(), acceptOutdated(0), now().minusSeconds(120)) + ); + /* @formatter:on */ + + } + + } + + static class OutdatedEncryptionPoolInClusterPossibleArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + /* @formatter:off */ + return Stream.of( + /*create pool entry now + * |------20s-----------------------| + * | |<--5s+1s(+1)--->| (7s) + * |<-----13s----->| + * | V + * | last refresh point (calculated) + * <------- > 13----> acceptance time is bigger -> still possible + */ + Arguments.of(now(), acceptOutdated(14), now().minusSeconds(20), refreshInterval(5), refresInitialDelay(1)), + Arguments.of(now(), acceptOutdated(15), now().minusSeconds(20), refreshInterval(5), refresInitialDelay(1)), + Arguments.of(now(), acceptOutdated(100), now().minusSeconds(20), refreshInterval(5), refresInitialDelay(1)), + + /*create pool entry now + * |------60s-----------------------| + * | |<--25s+10s(+1)->| (36s) + * |<-----24s----->| + * | V + * | last refresh point (calculated) + * <------- > 24----> acceptance time is bigger -> still possible + */ + Arguments.of(now(), acceptOutdated(25), now().minusSeconds(60), refreshInterval(25), refresInitialDelay(10)), + Arguments.of(now(), acceptOutdated(37), now().minusSeconds(60), refreshInterval(25), refresInitialDelay(10)), + Arguments.of(now(), acceptOutdated(60), now().minusSeconds(60), refreshInterval(25), refresInitialDelay(10)), + + /*create pool entry now + * |------120s----------------------| + * | |<--15s+3s(+1)- >| (19s) + * |<-----101s---->| + * | V + * | last refresh point (calculated) + * <------- > 101----> acceptance time is bigger -> still possible + */ + Arguments.of(now(), acceptOutdated(102), now().minusSeconds(120), refreshInterval(15), refresInitialDelay(3)), + Arguments.of(now(), acceptOutdated(210), now().minusSeconds(120), refreshInterval(15), refresInitialDelay(3)), + Arguments.of(now(), acceptOutdated(10000), now().minusSeconds(120), refreshInterval(15), refresInitialDelay(3)) + ); + /* @formatter:on */ + + } + + } + + static class OutdatedEncryptionPoolInClusterNotPossibleArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + /* @formatter:off */ + return Stream.of( + /*create pool entry now + * |------20s-----------------------| + * | |<--5s+1s(+1)--->| + * |<-----13s----->| + * | V + * | last refresh point (calculated) + * <----- < 13--> acceptance time is lower -> NOT possible + * + */ + Arguments.of(now(), acceptOutdated(12), now().minusSeconds(20), refreshInterval(5), refresInitialDelay(1)), + Arguments.of(now(), acceptOutdated(11), now().minusSeconds(20), refreshInterval(5), refresInitialDelay(1)), + Arguments.of(now(), acceptOutdated(10), now().minusSeconds(20), refreshInterval(5), refresInitialDelay(1)), + + /*create pool entry now + * |------60s-----------------------| + * | |<--25s+10s(+1)->| (36s) + * |<-----24s----->| + * | V + * | last refresh point (calculated) + * <------- < 24----> acceptance time is lower -> NOT possible + */ + Arguments.of(now(), acceptOutdated(23), now().minusSeconds(60), refreshInterval(25), refresInitialDelay(10)), + Arguments.of(now(), acceptOutdated(12), now().minusSeconds(60), refreshInterval(25), refresInitialDelay(10)), + Arguments.of(now(), acceptOutdated(1), now().minusSeconds(60), refreshInterval(25), refresInitialDelay(10)), + + /*create pool entry now + * |------120s----------------------| + * | |<--15s+3s(+1)- >| (19s) + * |<-----101s---->| + * | V + * | last refresh point (calculated) + * <------- < 101----> acceptance time is lower -> NOT possible + */ + Arguments.of(now(), acceptOutdated(100), now().minusSeconds(120), refreshInterval(15), refresInitialDelay(3)), + Arguments.of(now(), acceptOutdated(18), now().minusSeconds(120), refreshInterval(15), refresInitialDelay(3)), + Arguments.of(now(), acceptOutdated(10), now().minusSeconds(120), refreshInterval(15), refresInitialDelay(3)), + Arguments.of(now(), acceptOutdated(0), now().minusSeconds(120), refreshInterval(15), refresInitialDelay(3)), + + /* create pool entry now + * v v + * |------1s------------------------| + * | <--21s+5s(+1)- >| (27s)| + * -----------------------|<-----1s----------------------->| + * | + * | V (26 seconds before creation time) (-26s) + * | last refresh point (calculated) + * <------- < -26----> acceptance time is lower -> NOT possible + */ + Arguments.of(now(), acceptOutdated(10), now().minusSeconds(1), refreshInterval(21), refresInitialDelay(5)), + Arguments.of(now(), acceptOutdated(25), now().minusSeconds(1), refreshInterval(21), refresInitialDelay(5)) + ); + /* @formatter:on */ + + } + + } + + /*** + * Some syntax sugar for test data - define accepted outdated time in SECONDS + * + * @param seconds define seconds + * + * @return milliseconds + */ + private static long acceptOutdated(long seconds) { + return seconds * 1000; + } + + /** + * Some syntax sugar for test data - define refresh interval in SECONDS * @param + * seconds define seconds + * + * @return milliseconds + */ + private static long refreshInterval(long seconds) { + return seconds * 1000; + } + + /** + * Some syntax sugar for test data - define refresh initial delay in SECONDS + * + * @param seconds define seconds + * @return milliseconds + */ + private static long refresInitialDelay(long seconds) { + return seconds * 1000; + } +} diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java index 6e2558708a..e36d04f339 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobCreator.java @@ -8,10 +8,15 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import com.mercedesbenz.sechub.commons.encryption.EncryptionConstants; +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; import com.mercedesbenz.sechub.commons.model.ModuleGroup; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelSupport; import com.mercedesbenz.sechub.commons.model.job.ExecutionResult; import com.mercedesbenz.sechub.commons.model.job.ExecutionState; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionResult; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.sharedkernel.UserContextService; import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; import com.mercedesbenz.sechub.test.SechubTestComponent; @@ -25,6 +30,7 @@ public class JobCreator { private String projectId; private UserContextService userContextService; private SecHubConfigurationModelSupport modelSupport; + private ScheduleEncryptionService encryptionService; private JobCreator(String projectId, TestEntityManager entityManager) { this.modelSupport = new SecHubConfigurationModelSupport(); @@ -33,10 +39,20 @@ private JobCreator(String projectId, TestEntityManager entityManager) { this.jobFactory = new SecHubJobFactory(); userContextService = mock(UserContextService.class); + encryptionService = mock(ScheduleEncryptionService.class); this.jobFactory.userContextService = userContextService; this.jobFactory.modelSupport = modelSupport; + this.jobFactory.encryptionService = encryptionService; when(userContextService.getUserId()).thenReturn("loggedInUser"); + doAnswer(invocation -> { + String textToEncrypt = invocation.getArgument(0); + + EncryptionResult encryptionResult = new EncryptionResult(textToEncrypt.getBytes(EncryptionConstants.UTF8_CHARSET_ENCODING), + new InitializationVector(new byte[] {})); + ScheduleEncryptionResult result = new ScheduleEncryptionResult(Long.valueOf(0), encryptionResult); + return result; + }).when(encryptionService).encryptWithLatestCipher(any()); newJob(); } @@ -75,6 +91,11 @@ public JobCreator project(String projectId) { return this; } + public JobCreator encryptionPoolId(Long encryptionCipherPoolId) { + job.encryptionCipherPoolId = encryptionCipherPoolId; + return this; + } + /** * Creates the job and returns builder agani * diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobFactoryTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobFactoryTest.java index c97c5e2256..b180e7517b 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobFactoryTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/JobFactoryTest.java @@ -1,46 +1,97 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.schedule.job; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelSupport; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionResult; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.sharedkernel.UserContextService; import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; -import com.mercedesbenz.sechub.test.junit4.ExpectedExceptionFactory; public class JobFactoryTest { + private static final String UNENCRYPTED_CONFIGURATION = "i-am-the-origin-configuration"; + private static final Long LATEST_CIPHER_POOL_ID = Long.valueOf(1234); + private static final byte[] ENCRYPTED_CONFIGURATION_DATA = "encrypted-data".getBytes(); + private static byte[] ENCRYPTION_INITIAL_VECTOR = "initial-vector".getBytes(); + private SecHubJobFactory factoryToTest; private SecHubConfiguration configuration; - @Rule - public ExpectedException expected = ExpectedExceptionFactory.none(); + private ScheduleEncryptionService encryptionService; + + @BeforeEach + void beforeEach() { - @Before - public void before() { factoryToTest = new SecHubJobFactory(); + + encryptionService = mock(ScheduleEncryptionService.class); + configuration = mock(SecHubConfiguration.class); + when(configuration.toJSON()).thenReturn(UNENCRYPTED_CONFIGURATION); + factoryToTest.userContextService = mock(UserContextService.class); factoryToTest.modelSupport = mock(SecHubConfigurationModelSupport.class); + factoryToTest.encryptionService = encryptionService; + + ScheduleEncryptionResult encryptionResult = mock(ScheduleEncryptionResult.class); + when(encryptionResult.getCipherPoolId()).thenReturn(LATEST_CIPHER_POOL_ID); + when(encryptionResult.getEncryptedData()).thenReturn(ENCRYPTED_CONFIGURATION_DATA); + when(encryptionResult.getInitialVector()).thenReturn(new InitializationVector(ENCRYPTION_INITIAL_VECTOR)); + + when(encryptionService.encryptWithLatestCipher(UNENCRYPTED_CONFIGURATION)).thenReturn(encryptionResult); + + when(factoryToTest.userContextService.getUserId()).thenReturn("user1"); } @Test - public void new_jobs_have_creation_time_stamp() throws Exception { - /* prepare */ - when(factoryToTest.userContextService.getUserId()).thenReturn("hugo"); + void new_jobs_have_creation_time_stamp() throws Exception { + + /* execute */ + ScheduleSecHubJob job = factoryToTest.createJob(configuration); + + /* test */ + assertThat(job).isNotNull(); + assertThat(job.getCreated()).isNotNull(); + + } + + @Test + void new_jobs_have_configuration_encrypted_by_encryption_service() throws Exception { + + /* execute */ + ScheduleSecHubJob job = factoryToTest.createJob(configuration); + + /* test */ + assertThat(job.getEncryptedConfiguration()).isEqualTo(ENCRYPTED_CONFIGURATION_DATA); + + } + + @Test + void new_jobs_have_encryption_initial_vector_from_encryption_service() throws Exception { /* execute */ ScheduleSecHubJob job = factoryToTest.createJob(configuration); /* test */ - assertNotNull(job); - assertNotNull(job.getCreated()); + assertThat(job.getEncryptionInitialVectorData()).isEqualTo(ENCRYPTION_INITIAL_VECTOR); + + } + + @Test + void new_jobs_have_lastet_encryption_pool_id_from_encryption_service() throws Exception { + + /* execute */ + ScheduleSecHubJob job = factoryToTest.createJob(configuration); + + /* test */ + assertThat(job.getEncryptionCipherPoolId()).isEqualTo(LATEST_CIPHER_POOL_ID); } @@ -49,11 +100,8 @@ public void factory_throws_illegal_state_exception_when_no_user() throws Excepti /* prepare */ when(factoryToTest.userContextService.getUserId()).thenReturn(null); - /* test */ - expected.expect(IllegalStateException.class); - /* execute */ - factoryToTest.createJob(configuration); + assertThatThrownBy(()-> factoryToTest.createJob(configuration)).isInstanceOf(IllegalStateException.class); } diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactoryTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactorySpringBootTest.java similarity index 74% rename from sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactoryTest.java rename to sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactorySpringBootTest.java index 8280db56fb..1e90cb0f67 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactoryTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobFactorySpringBootTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.nio.charset.Charset; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; @@ -12,27 +13,51 @@ import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; import com.mercedesbenz.sechub.commons.model.ModuleGroup; import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModelSupport; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionResult; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.sharedkernel.UserContextService; import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; -class SecHubJobFactoryTest { +@SpringBootTest(classes = SecHubJobFactory.class) +class SecHubJobFactorySpringBootTest { + @Autowired private SecHubJobFactory factoryToTest; + + @MockBean private UserContextService userContextService; + + @MockBean private SecHubConfigurationModelSupport modelSupport; + @MockBean(name = "encryption service") + private ScheduleEncryptionService encryptionService; + + private InitializationVector initialVector; + + private byte[] encryptedData; + + private Long cipherPoolId; + @BeforeEach void beforeEach() { - userContextService = mock(UserContextService.class); - modelSupport = mock(SecHubConfigurationModelSupport.class); - - factoryToTest = new SecHubJobFactory(); - factoryToTest.userContextService = userContextService; - factoryToTest.modelSupport = modelSupport; + encryptedData = "i-am-encrypted".getBytes(Charset.forName("UTF-8")); + cipherPoolId = Long.valueOf(0); + initialVector = new InitializationVector("init-vector".getBytes()); + + ScheduleEncryptionResult result = mock(ScheduleEncryptionResult.class); + when(encryptionService.encryptWithLatestCipher(any())).thenReturn(result); + when(result.getInitialVector()).thenReturn(initialVector); + when(result.getEncryptedData()).thenReturn(encryptedData); + when(result.getCipherPoolId()).thenReturn(cipherPoolId); } @ParameterizedTest @@ -78,7 +103,7 @@ void createJob_sets_creation_time() { } @Test - void createJob_sets_configuration_as_json() { + void createJob_sets_configuration_encrypted() { /* prepare */ when(userContextService.getUserId()).thenReturn("user1"); SecHubConfiguration configuration = mock(SecHubConfiguration.class); @@ -89,7 +114,8 @@ void createJob_sets_configuration_as_json() { /* test */ assertNotNull(result); - assertEquals("pseudo-json", result.getJsonConfiguration()); + assertEquals(encryptedData, result.getEncryptedConfiguration()); + verify(encryptionService).encryptWithLatestCipher("pseudo-json"); } @Test diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserServiceTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserServiceTest.java index 9435fcba90..db149f3f03 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserServiceTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobInfoForUserServiceTest.java @@ -37,6 +37,7 @@ class SecHubJobInfoForUserServiceTest { private SecHubJobInfoForUserService serviceToTest; private SecHubJobRepository jobRepository; private ScheduleAssertService assertService; + private SecHubConfigurationModelAccessService configurationModelAccess; @BeforeEach void beforeEach() { @@ -44,12 +45,15 @@ void beforeEach() { jobRepository = mock(SecHubJobRepository.class); assertService = mock(ScheduleAssertService.class); + configurationModelAccess = mock(SecHubConfigurationModelAccessService.class); + serviceToTest = new SecHubJobInfoForUserService(); serviceToTest.jobRepository = jobRepository; serviceToTest.assertService = assertService; serviceToTest.metaDataTransformer = new SecHubConfigurationMetaDataMapTransformer(); serviceToTest.modelValidator = new SecHubConfigurationModelValidator(); + serviceToTest.configurationModelAccess = configurationModelAccess; } @@ -290,7 +294,9 @@ private ScheduleSecHubJob createJob(TrafficLight trafficLight, ExecutionResult r SecHubConfigurationMetaData metaData = new SecHubConfigurationMetaData(); metaData.getLabels().put("testlabel1", "testvalue1"); config.setMetaData(metaData); - sechubJob.jsonConfiguration = config.toJSON(); + + // simulate encryption done by job creation factory + when(configurationModelAccess.resolveUnencryptedConfiguration(sechubJob)).thenReturn(config); } return sechubJob; diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java index 99e6633331..fe87a3f8d4 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/job/SecHubJobRepositoryDBTest.java @@ -4,6 +4,7 @@ import static com.mercedesbenz.sechub.commons.model.job.ExecutionState.*; import static com.mercedesbenz.sechub.domain.schedule.job.JobCreator.*; import static com.mercedesbenz.sechub.test.FlakyOlderThanTestWorkaround.*; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; import java.time.LocalDateTime; @@ -11,13 +12,21 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +47,8 @@ @ContextConfiguration(classes = { SecHubJobRepository.class, SecHubJobRepositoryDBTest.SimpleTestConfiguration.class }) public class SecHubJobRepositoryDBTest { + private static final Set FIRST_ENCRYPTION_POOL_ENTRY_ONLY = Set.of(0L); + @Autowired private TestEntityManager entityManager; @@ -51,6 +62,208 @@ void before() { jobCreator = jobCreator("p0", entityManager); } + @Test + void collectAllUsedEncryptionPoolIdsInsideJobs_no_jobs_found() { + /* execute */ + List result = jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs(); + + /* test */ + assertThat(result).isNotNull().isEmpty(); + } + + @ParameterizedTest + @EnumSource(ExecutionState.class) + void collectAllUsedEncryptionPoolIdsInsideJobs_only_two_jobs_found_same_poolid(ExecutionState stateOfJob) { + /* prepare */ + int poolId = 1234; + createJobUsingEncryptionPoolId(poolId, stateOfJob); + createJobUsingEncryptionPoolId(poolId, stateOfJob); + + /* execute */ + List result = jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs(); + + /* test */ + assertThat(result).isNotNull().hasSize(1); + Long value = result.iterator().next(); + + assertThat(value).isEqualTo(poolId); + } + + @Test + void collectAllUsedEncryptionPoolIdsInsideJobs_multiple_jobs_mixed_poolids() { + /* prepare */ + createJobUsingEncryptionPoolId(0, ExecutionState.INITIALIZING); + createJobUsingEncryptionPoolId(1, ExecutionState.READY_TO_START); + createJobUsingEncryptionPoolId(1, ExecutionState.CANCEL_REQUESTED); + createJobUsingEncryptionPoolId(3, ExecutionState.ENDED); + createJobUsingEncryptionPoolId(176, ExecutionState.STARTED); + createJobUsingEncryptionPoolId(176, ExecutionState.CANCELED); + + /* execute */ + List result = jobRepository.collectAllUsedEncryptionPoolIdsInsideJobs(); + + /* test */ + assertThat(result).isNotNull().hasSize(4).contains(0L, 1L, 3L, 176L); + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2, 10 }) + void countCanceledOrEndedJobsWithEncryptionPoolIdLowerThan_works_as_expected(int expectedResultCount) { + /* prepare */ + jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.INITIALIZING).create(); + jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.STARTED).create(); + jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCEL_REQUESTED).create(); + + // generate data + if (expectedResultCount > 0) { + jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + } + if (expectedResultCount > 1) { + for (int i = 1; i < expectedResultCount; i++) { + jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCELED).create(); + } + } + + /* execute */ + long result = jobRepository.countCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(1L); + + /* test */ + assertEquals(result, expectedResultCount); + } + + @ParameterizedTest + @ValueSource(ints = { 1, 2, 4 }) + void nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan_one_ended_only_single_entry_always_returned(int amount) { + /* prepare */ + ScheduleSecHubJob newJob1 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.STARTED).create(); + ScheduleSecHubJob newJob2 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob3 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.INITIALIZING).create(); + ScheduleSecHubJob newJob4 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob5 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCEL_REQUESTED).create(); + + // check preconditions + assertEquals(0, newJob1.getEncryptionCipherPoolId()); + assertEquals(0, newJob2.getEncryptionCipherPoolId()); + assertEquals(0, newJob3.getEncryptionCipherPoolId()); + assertEquals(0, newJob4.getEncryptionCipherPoolId()); + assertEquals(0, newJob5.getEncryptionCipherPoolId()); + + /* execute */ + List list = jobRepository.nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(1L, amount); + + /* test */ + assertFalse(list.isEmpty()); + assertTrue(list.contains(newJob4)); // only this, because others are in wrong state + assertEquals(1, list.size()); + } + + @ParameterizedTest + @ValueSource(ints = { 1, 2, 4 }) + void nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan_one_is_lower_ended_only_single_entry_always_returned(int amount) { + /* prepare */ + ScheduleSecHubJob newJob1 = jobCreator.project("p1").module(ModuleGroup.STATIC).encryptionPoolId(0L).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob2 = jobCreator.project("p1").module(ModuleGroup.STATIC).encryptionPoolId(1L).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob3 = jobCreator.project("p2").module(ModuleGroup.STATIC).encryptionPoolId(1L).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob4 = jobCreator.project("p2").module(ModuleGroup.STATIC).encryptionPoolId(2L).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob5 = jobCreator.project("p2").module(ModuleGroup.STATIC).encryptionPoolId(3L).being(ExecutionState.ENDED).create(); + + // check preconditions + assertEquals(0, newJob1.getEncryptionCipherPoolId()); + assertEquals(1, newJob2.getEncryptionCipherPoolId()); + assertEquals(1, newJob3.getEncryptionCipherPoolId()); + assertEquals(2, newJob4.getEncryptionCipherPoolId()); + assertEquals(3, newJob5.getEncryptionCipherPoolId()); + + /* execute */ + List list = jobRepository.nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(1L, amount); + + /* test */ + assertFalse(list.isEmpty()); + assertTrue(list.contains(newJob1)); // only this, because others are already with higher pool id + assertEquals(1, list.size()); + } + + @ParameterizedTest + @ValueSource(ints = { 2, 10, 100 }) + void nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan_one_ended_one_canceled_entries_always_returned(int amount) { + /* prepare */ + ScheduleSecHubJob newJob1 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.STARTED).create(); + ScheduleSecHubJob newJob2 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob3 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.INITIALIZING).create(); + ScheduleSecHubJob newJob4 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob5 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCEL_REQUESTED).create(); + ScheduleSecHubJob newJob6 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCELED).create(); + + // check preconditions + assertEquals(0, newJob1.getEncryptionCipherPoolId()); + assertEquals(0, newJob2.getEncryptionCipherPoolId()); + assertEquals(0, newJob3.getEncryptionCipherPoolId()); + assertEquals(0, newJob4.getEncryptionCipherPoolId()); + assertEquals(0, newJob5.getEncryptionCipherPoolId()); + assertEquals(0, newJob6.getEncryptionCipherPoolId()); + + /* execute */ + List list = jobRepository.nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(1L, amount); + + /* test */ + assertFalse(list.isEmpty()); + assertTrue(list.contains(newJob4)); + assertTrue(list.contains(newJob6)); + assertEquals(2, list.size()); + } + + @Test + void nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan_randomization_works() { + /* prepare */ + ScheduleSecHubJob newJob1 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob2 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.CANCELED).create(); + ScheduleSecHubJob newJob3 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob4 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCELED).create(); + ScheduleSecHubJob newJob5 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob6 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCELED).create(); + + // check preconditions + assertEquals(0, newJob1.getEncryptionCipherPoolId()); + assertEquals(0, newJob2.getEncryptionCipherPoolId()); + assertEquals(0, newJob3.getEncryptionCipherPoolId()); + assertEquals(0, newJob4.getEncryptionCipherPoolId()); + assertEquals(0, newJob5.getEncryptionCipherPoolId()); + assertEquals(0, newJob6.getEncryptionCipherPoolId()); + + Map map = new LinkedHashMap<>(); + + /* execute */ + for (int i = 0; i < 30; i++) { + List list = jobRepository.nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(1L, 1); + for (ScheduleSecHubJob job : list) { + AtomicInteger atomic = map.computeIfAbsent(job.getUUID(), (uuid) -> new AtomicInteger(0)); + atomic.incrementAndGet(); + } + } + + /* test */ + assertEquals(6, map.size()); // when calling n times, we expect every entry is contained + System.out.println("map:" + map); + + } + + @Test + void nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan_2_entries_always_returned() { + /* prepare */ + jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.STARTED).create(); + jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCEL_REQUESTED).create(); + ScheduleSecHubJob newJob4 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + + /* execute */ + List list = jobRepository.nextCanceledOrEndedJobsWithEncryptionPoolIdLowerThan(1L, 10); + + /* test */ + assertFalse(list.isEmpty()); + assertTrue(list.contains(newJob4)); + } + @Test void findAll_with_specifications_for_project_id_and_data_2_data_but_only_one_matches() { @@ -352,17 +565,13 @@ void delete_job_with_data_deletes_data() { assertNull(result); } - private ScheduleSecHubJobData findDataOrNullByJobUUID(String key, UUID jobUUID) { - return entityManager.find(ScheduleSecHubJobData.class, new ScheduleSecHubJobDataId(jobUUID, key)); - } - @Test void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted() { /* prepare */ ScheduleSecHubJob newJob = jobCreator.being(ExecutionState.READY_TO_START).create(); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -375,7 +584,7 @@ void custom_query_nextJobIdToExecuteForProjectNotYetExecuted_one_available() { ScheduleSecHubJob newJob = jobCreator.being(ExecutionState.READY_TO_START).create(); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(Set.of(0L)); /* test */ assertTrue(uuid.isPresent()); @@ -394,7 +603,7 @@ void custom_query_nextJobIdToExecuteForProjectNotYetExecuted_2_projects_1_projec assertTrue(newJob3.created.isAfter(newJob2.created)); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(Set.of(0L)); /* test */ assertTrue(uuid.isPresent()); @@ -413,7 +622,7 @@ void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted_2_pro assertTrue(newJob3.created.isAfter(newJob2.created)); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -432,7 +641,7 @@ void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted_2_pro assertTrue(newJob3.created.isAfter(newJob2.created)); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -453,7 +662,7 @@ void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted_3_pro assertTrue(newJob4.created.isAfter(newJob3.created)); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -474,7 +683,7 @@ void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted_3_pro assertTrue(newJob4.created.isAfter(newJob3.created)); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -495,7 +704,7 @@ void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted_4_pro assertTrue(newJob4.created.isAfter(newJob3.created)); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -520,7 +729,7 @@ void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted_3_pro assertTrue(newJob6.created.isAfter(newJob5.created)); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + Optional uuid = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -533,7 +742,7 @@ void custom_query_nextJobIdToExecuteFirstInFirstOut() { ScheduleSecHubJob newJob = jobCreator.being(ExecutionState.READY_TO_START).create(); /* execute */ - Optional uuid = jobRepository.nextJobIdToExecuteFirstInFirstOut(); + Optional uuid = jobRepository.nextJobIdToExecuteFirstInFirstOut(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); /* test */ assertTrue(uuid.isPresent()); @@ -717,7 +926,7 @@ void findByUUID__the_job_is_returned_when_existing() { @Test void findNextJobToExecute__and_no_jobs_available_at_all_null_is_returned_when_existing() { - assertFalse(jobRepository.nextJobIdToExecuteFirstInFirstOut().isPresent()); + assertFalse(jobRepository.nextJobIdToExecuteFirstInFirstOut(FIRST_ENCRYPTION_POOL_ENTRY_ONLY).isPresent()); } @Test @@ -726,7 +935,7 @@ void findNextJobToExecute__and_no_executable_job_available_at_all_null_is_return jobCreator.newJob().being(STARTED).create(); /* execute + test */ - assertFalse(jobRepository.nextJobIdToExecuteFirstInFirstOut().isPresent()); + assertFalse(jobRepository.nextJobIdToExecuteFirstInFirstOut(FIRST_ENCRYPTION_POOL_ENTRY_ONLY).isPresent()); } @@ -749,16 +958,210 @@ void findNextJobToExecute__the_first_job_in_state_READY_TO_START_is_returned_whe newJob().being(READY_TO_START).create(); /* execute */ - Optional optional = jobRepository.nextJobIdToExecuteFirstInFirstOut(); + Optional optional = jobRepository.nextJobIdToExecuteFirstInFirstOut(FIRST_ENCRYPTION_POOL_ENTRY_ONLY); assertTrue(optional.isPresent()); UUID jobUUID = optional.get(); /* test @formatter:on*/ - assertNotNull(jobUUID); + assertThat(jobUUID).isNotNull(); assertEquals(expectedNextJob.getUUID(), jobUUID); } + @ParameterizedTest + @ArgumentsSource(NextJobIdToExecuteWithEncryptionPoolTestDataArgumentProvider.class) + void nextJobIdToExecuteForProjectNotYetExecuted_2_projects_1_project_running(Set currentEncryptionPoolIds, TestResultVariant expectedVariant) { + /* prepare */ + ScheduleSecHubJob newJob1 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.STARTED).create(); + ScheduleSecHubJob newJob2 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob3p0 = jobCreator.project("p2").encryptionPoolId(0L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob3p1 = jobCreator.project("p2").encryptionPoolId(1L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob3p2 = jobCreator.project("p2").encryptionPoolId(2L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob3p3 = jobCreator.project("p2").encryptionPoolId(3L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + + /* check preconditions */ + assertTrue(newJob2.created.isAfter(newJob1.created)); + assertTrue(newJob3p0.created.isAfter(newJob2.created)); + + /* execute */ + Optional optional = jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(currentEncryptionPoolIds); + if (expectedVariant == null || TestResultVariant.NONE.equals(expectedVariant)) { + assertFalse(optional.isPresent()); + return; + } + assertTrue(optional.isPresent()); + + UUID jobUUID = optional.get(); + + /* test @formatter:on*/ + assertThat(jobUUID).isNotNull(); + switch (expectedVariant) { + case POOL_ID_0: + assertThat(jobUUID).isEqualTo(newJob3p0.getUUID()); + break; + case POOL_ID_1: + assertThat(jobUUID).isEqualTo(newJob3p1.getUUID()); + break; + case POOL_ID_2: + assertThat(jobUUID).isEqualTo(newJob3p2.getUUID()); + break; + case POOL_ID_3: + assertThat(jobUUID).isEqualTo(newJob3p3.getUUID()); + break; + default: + throw new IllegalStateException("not implemented for variant: " + expectedVariant); + + } + } + + @ParameterizedTest + @ArgumentsSource(NextJobIdToExecuteWithEncryptionPoolTestDataArgumentProvider.class) + void nextJobIdToExecuteFirstInFirstOut__the_job_with_has_supported_encryption_in_state_READY_TO_START_multi_poolids(Set currentEncryptionPoolIds, + TestResultVariant expectedVariant) { + /* prepare @formatter:off*/ + + jobCreator.newJob().being(STARTED).createAnd(). + newJob().being(CANCEL_REQUESTED).createAnd(). + newJob().being(CANCELED).createAnd(). + newJob().being(ENDED).create(); + ScheduleSecHubJob expectedNextJobWhenPoolId0Supported = + jobCreator.newJob().encryptionPoolId(0L).being(READY_TO_START).create(); + ScheduleSecHubJob expectedNextJobWhenPoolId1Supported = + jobCreator.newJob().encryptionPoolId(1L).being(READY_TO_START).create(); + ScheduleSecHubJob expectedNextJobWhenPoolId2Supported = + jobCreator.newJob().being(READY_TO_START).encryptionPoolId(2L).create(); + ScheduleSecHubJob expectedNextJobWhenPoolId3Supported = + jobCreator.newJob().being(READY_TO_START).encryptionPoolId(3L).create(); + + TestUtil.waitMilliseconds(1); // just enough time to make the next job "older" than former one, so we got no flaky tests when checking jobUUID later + + + jobCreator.newJob().being(STARTED).createAnd(). + newJob().being(READY_TO_START).create(); + + /* execute */ + Optional optional = jobRepository.nextJobIdToExecuteFirstInFirstOut(currentEncryptionPoolIds); + if (expectedVariant==null || TestResultVariant.NONE.equals(expectedVariant)) { + assertFalse(optional.isPresent()); + return; + } + assertTrue(optional.isPresent()); + + UUID jobUUID = optional.get(); + + /* test @formatter:on*/ + assertThat(jobUUID).isNotNull(); + switch (expectedVariant) { + case POOL_ID_0: + assertEquals(expectedNextJobWhenPoolId0Supported.getUUID(), jobUUID); + break; + case POOL_ID_1: + assertEquals(expectedNextJobWhenPoolId1Supported.getUUID(), jobUUID); + break; + case POOL_ID_2: + assertEquals(expectedNextJobWhenPoolId2Supported.getUUID(), jobUUID); + break; + case POOL_ID_3: + assertEquals(expectedNextJobWhenPoolId3Supported.getUUID(), jobUUID); + break; + default: + throw new IllegalStateException("not implemented for variant: " + expectedVariant); + + } + } + + @ParameterizedTest + @ArgumentsSource(NextJobIdToExecuteWithEncryptionPoolTestDataArgumentProvider.class) + void custom_query_nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted_3_projects_in_state_READY_TO_START_multi_poolids( + Set currentEncryptionPoolIds, TestResultVariant expectedVariant) { + /* prepare */ + ScheduleSecHubJob newJob1 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.CANCEL_REQUESTED).create(); + ScheduleSecHubJob newJob2 = jobCreator.project("p1").module(ModuleGroup.STATIC).being(ExecutionState.INITIALIZING).create(); + ScheduleSecHubJob newJob3 = jobCreator.project("p2").module(ModuleGroup.STATIC).being(ExecutionState.CANCELED).create(); + ScheduleSecHubJob newJob4 = jobCreator.project("p3").module(ModuleGroup.STATIC).being(ExecutionState.ENDED).create(); + ScheduleSecHubJob newJob5 = jobCreator.project("p3").module(ModuleGroup.STATIC).being(ExecutionState.CANCEL_REQUESTED).create(); + + ScheduleSecHubJob newJob6v0 = jobCreator.project("p3").encryptionPoolId(0L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob6v1 = jobCreator.project("p3").encryptionPoolId(1L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob6v2 = jobCreator.project("p3").encryptionPoolId(2L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + ScheduleSecHubJob newJob6v3 = jobCreator.project("p3").encryptionPoolId(3L).module(ModuleGroup.STATIC).being(ExecutionState.READY_TO_START).create(); + + /* check preconditions */ + assertTrue(newJob2.created.isAfter(newJob1.created)); + assertTrue(newJob3.created.isAfter(newJob2.created)); + assertTrue(newJob4.created.isAfter(newJob3.created)); + assertTrue(newJob5.created.isAfter(newJob4.created)); + assertTrue(newJob6v0.created.isAfter(newJob5.created)); + + /* execute */ + Optional optional = jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(currentEncryptionPoolIds); + + /* test */ + if (expectedVariant == null || TestResultVariant.NONE.equals(expectedVariant)) { + assertThat(optional).isNotPresent(); + return; + } + assertTrue(optional.isPresent()); + + UUID jobUUID = optional.get(); + + /* test @formatter:on*/ + assertThat(jobUUID).isNotNull(); + switch (expectedVariant) { + case POOL_ID_0: + assertEquals(newJob6v0.getUUID(), jobUUID); + break; + case POOL_ID_1: + assertEquals(newJob6v1.getUUID(), jobUUID); + break; + case POOL_ID_2: + assertEquals(newJob6v2.getUUID(), jobUUID); + break; + case POOL_ID_3: + assertEquals(newJob6v3.getUUID(), jobUUID); + break; + default: + throw new IllegalStateException("not implemented for variant: " + expectedVariant); + + } + } + + private enum TestResultVariant { + NONE, POOL_ID_0, POOL_ID_1, POOL_ID_2, POOL_ID_3; + } + + static class NextJobIdToExecuteWithEncryptionPoolTestDataArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of( + /* @formatter:off */ + Arguments.of(Set.of(), TestResultVariant.NONE), + Arguments.of(Set.of(0), TestResultVariant.POOL_ID_0), + Arguments.of(Set.of(1), TestResultVariant.POOL_ID_1), + Arguments.of(Set.of(2), TestResultVariant.POOL_ID_2), + Arguments.of(Set.of(3), TestResultVariant.POOL_ID_3), + Arguments.of(Set.of(1,2), TestResultVariant.POOL_ID_1), + Arguments.of(Set.of(0,1,2,3), TestResultVariant.POOL_ID_0), + Arguments.of(Set.of(1,2,3), TestResultVariant.POOL_ID_1), + Arguments.of(Set.of(3,2,0), TestResultVariant.POOL_ID_0), + Arguments.of(Set.of(0,3),TestResultVariant.POOL_ID_0) + ); + /* @formatter:on */ + + } + + } + + private ScheduleSecHubJobData findDataOrNullByJobUUID(String key, UUID jobUUID) { + return entityManager.find(ScheduleSecHubJobData.class, new ScheduleSecHubJobDataId(jobUUID, key)); + } + + private void createJobUsingEncryptionPoolId(long poolId, ExecutionState state) { + jobCreator.project("p2").module(ModuleGroup.STATIC).being(state).encryptionPoolId(poolId).create(); + + } + private void assertDeleted(int expected, int deleted, DeleteJobTestData testData, LocalDateTime olderThan) { if (deleted == expected) { return; @@ -778,7 +1181,7 @@ private void assertDeleted(int expected, int deleted, DeleteJobTestData testData sb.append(describe(testData.job3_1_day_before_created, testData)); sb.append(describe(testData.job4_now_created, testData)); - fail(sb.toString()); + throw new AssertionError(sb.toString()); } private String describe(ScheduleSecHubJob info, DeleteJobTestData data) { diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java index f53f53ca1b..6fa219c6ef 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/FirstComeFirstServeSchedulerStrategyTest.java @@ -4,12 +4,15 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; class FirstComeFirstServeSchedulerStrategyTest { @@ -17,27 +20,33 @@ class FirstComeFirstServeSchedulerStrategyTest { private FirstComeFirstServeSchedulerStrategy strategyToTest; private SecHubJobRepository jobRepository; private UUID jobUUID; + private ScheduleEncryptionService encryptionService; @BeforeEach void beforeEach() { jobUUID = UUID.randomUUID(); jobRepository = mock(SecHubJobRepository.class); + encryptionService = mock(ScheduleEncryptionService.class); + strategyToTest = new FirstComeFirstServeSchedulerStrategy(); strategyToTest.jobRepository = jobRepository; + strategyToTest.encryptionService = encryptionService; } @Test void nextJobId_calls_expected_query_method() { /* prepare */ - when(jobRepository.nextJobIdToExecuteFirstInFirstOut()).thenReturn(Optional.of(jobUUID)); + Set currentEncryptionPoolIds = Collections.emptySet(); + when(encryptionService.getCurrentEncryptionPoolIds()).thenReturn(currentEncryptionPoolIds); + when(jobRepository.nextJobIdToExecuteFirstInFirstOut(currentEncryptionPoolIds)).thenReturn(Optional.of(jobUUID)); /* execute */ UUID result = strategyToTest.nextJobId(); /* test */ assertEquals(jobUUID, result); - verify(jobRepository).nextJobIdToExecuteFirstInFirstOut(); + verify(jobRepository).nextJobIdToExecuteFirstInFirstOut(currentEncryptionPoolIds); } } diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java index 731c023a13..681ce41f86 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest.java @@ -4,12 +4,15 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; class OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest { @@ -17,27 +20,32 @@ class OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategyTest { private OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy strategyToTest; private SecHubJobRepository jobRepository; private UUID jobUUID; + private ScheduleEncryptionService encryptionService; @BeforeEach void beforeEach() { jobUUID = UUID.randomUUID(); jobRepository = mock(SecHubJobRepository.class); + encryptionService = mock(ScheduleEncryptionService.class); + strategyToTest = new OnlyOneScanPerProjectAndModuleGroupAtSameTimeStrategy(); strategyToTest.jobRepository = jobRepository; + strategyToTest.encryptionService = encryptionService; } @Test void nextJobId_calls_expected_query_method() { /* prepare */ - when(jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted()).thenReturn(Optional.of(jobUUID)); + Set supportedPoolIds = Collections.emptySet(); + when(jobRepository.nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(supportedPoolIds)).thenReturn(Optional.of(jobUUID)); /* execute */ UUID result = strategyToTest.nextJobId(); /* test */ assertEquals(jobUUID, result); - verify(jobRepository).nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(); + verify(jobRepository).nextJobIdToExecuteForProjectAndModuleGroupNotYetExecuted(supportedPoolIds); } } diff --git a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java index 3ed0fd7a06..5228496bb6 100644 --- a/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java +++ b/sechub-schedule/src/test/java/com/mercedesbenz/sechub/domain/schedule/strategy/OnlyOneScanPerProjectAtSameTimeStrategyTest.java @@ -4,12 +4,15 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; class OnlyOneScanPerProjectAtSameTimeStrategyTest { @@ -17,27 +20,32 @@ class OnlyOneScanPerProjectAtSameTimeStrategyTest { private OnlyOneScanPerProjectAtSameTimeStrategy strategyToTest; private SecHubJobRepository jobRepository; private UUID jobUUID; + private ScheduleEncryptionService encryptionService; @BeforeEach void beforeEach() { jobUUID = UUID.randomUUID(); jobRepository = mock(SecHubJobRepository.class); + encryptionService = mock(ScheduleEncryptionService.class); + strategyToTest = new OnlyOneScanPerProjectAtSameTimeStrategy(); strategyToTest.jobRepository = jobRepository; + strategyToTest.encryptionService = encryptionService; } @Test void nextJobId_calls_expected_query_method() { /* prepare */ - when(jobRepository.nextJobIdToExecuteForProjectNotYetExecuted()).thenReturn(Optional.of(jobUUID)); + Set set = Collections.emptySet(); + when(jobRepository.nextJobIdToExecuteForProjectNotYetExecuted(set)).thenReturn(Optional.of(jobUUID)); /* execute */ UUID result = strategyToTest.nextJobId(); /* test */ assertEquals(jobUUID, result); - verify(jobRepository).nextJobIdToExecuteForProjectNotYetExecuted(); + verify(jobRepository).nextJobIdToExecuteForProjectNotYetExecuted(set); } } diff --git a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/GitleaksSarifImportWorkaround.java b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/GitleaksSarifImportWorkaround.java index bbc6ca0908..131ace6d91 100644 --- a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/GitleaksSarifImportWorkaround.java +++ b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/GitleaksSarifImportWorkaround.java @@ -1,11 +1,18 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.sereco.importer; +import java.util.List; import java.util.Map; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.sereco.metadata.SerecoSeverity; + +import de.jcup.sarif_2_1_0.model.Location; import de.jcup.sarif_2_1_0.model.PartialFingerprints; +import de.jcup.sarif_2_1_0.model.PhysicalLocation; +import de.jcup.sarif_2_1_0.model.PropertyBag; +import de.jcup.sarif_2_1_0.model.Region; import de.jcup.sarif_2_1_0.model.ReportingDescriptor; import de.jcup.sarif_2_1_0.model.Result; import de.jcup.sarif_2_1_0.model.Run; @@ -42,6 +49,39 @@ public String resolveFindingRevisionId(Result result, Run run) { return null; } + @Override + public SerecoSeverity resolveCustomSerecoSeverity(Result result, Run run) { + if (result == null) { + return null; + } + if (!isGitleaksRun(run)) { + return null; + } + List locations = result.getLocations(); + if (locations == null || locations.isEmpty()) { + return null; + } + PhysicalLocation physicalLocation = locations.get(0).getPhysicalLocation(); + if (physicalLocation == null) { + return null; + } + Region region = physicalLocation.getRegion(); + if (region == null) { + return null; + } + PropertyBag properties = region.getProperties(); + if (properties == null) { + return null; + } + Map additionalProperties = properties.getAdditionalProperties(); + if (additionalProperties == null) { + return null; + } + String severityKey = SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey(); + String severityValue = (String) additionalProperties.get(severityKey); + return SerecoSeverity.fromString(severityValue); + } + private boolean isGitleaksRun(Run run) { if (run == null) { return false; diff --git a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaround.java b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaround.java index f4fdb73be4..d53b5ca2de 100644 --- a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaround.java +++ b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaround.java @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.sereco.importer; +import com.mercedesbenz.sechub.sereco.metadata.SerecoSeverity; + import de.jcup.sarif_2_1_0.model.ReportingDescriptor; import de.jcup.sarif_2_1_0.model.Result; import de.jcup.sarif_2_1_0.model.Run; @@ -22,4 +24,8 @@ public default String resolveType(ReportingDescriptor rule, Run run) { public default String resolveFindingRevisionId(Result result, Run run) { return null; } + + public default SerecoSeverity resolveCustomSerecoSeverity(Result result, Run run) { + return null; + } } diff --git a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaroundSupport.java b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaroundSupport.java index c88da22c92..1598b54808 100644 --- a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaroundSupport.java +++ b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImportProductWorkaroundSupport.java @@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.mercedesbenz.sechub.sereco.metadata.SerecoSeverity; + import de.jcup.sarif_2_1_0.model.ReportingDescriptor; import de.jcup.sarif_2_1_0.model.Result; import de.jcup.sarif_2_1_0.model.Run; @@ -58,6 +60,16 @@ private R visitAllWorkaroundsAndUseFirstResultNotNull(E element, Run run, return null; } + public SerecoSeverity resolveCustomSechubSeverity(Result result, Run run) { + return visitAllWorkaroundsAndUseFirstResultNotNull(result, run, new WorkaroundVisitor() { + + @Override + public SerecoSeverity visit(Result element, Run run, SarifImportProductWorkaround workaround) { + return workaround.resolveCustomSerecoSeverity(result, run); + } + }); + } + public interface WorkaroundVisitor { public R visit(E element, Run run, SarifImportProductWorkaround workaround); diff --git a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImporterKeys.java b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImporterKeys.java new file mode 100644 index 0000000000..dfb9c7da73 --- /dev/null +++ b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifImporterKeys.java @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sereco.importer; + +/* + * If this needs to get changed, make sure to change + * com.mercedesbenz.sechub.sereco.importer.SarifImporterKeys accordingly + */ +public enum SarifImporterKeys { + + SECRETSCAN_SERECO_SEVERITY("secretscan.sereco.severity", "The key for the sereco severity which is more precise than the SARIF Level enum."), + + SECRETSCAN_VALIDATED_BY_URL("secretscan.validated.by.url", "The key for the URL the secret was validated with."), + + ; + + private String key; + private String description; + + private SarifImporterKeys(String key, String description) { + this.key = key; + this.description = description; + } + + public String getKey() { + return key; + } + + public String getDescription() { + return description; + } +} diff --git a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporter.java b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporter.java index ab1205a593..e95b956b90 100644 --- a/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporter.java +++ b/sechub-sereco/src/main/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporter.java @@ -285,8 +285,12 @@ private void handleWebRequest(Result result, SerecoWeb serecoWeb) { } private SerecoSeverity resolveSeverity(Result result, Run run) { - Level level = sarifSchema210LogicSupport.resolveLevel(result, run); - return mapToSeverity(level); + SerecoSeverity serecoSeverity = workaroundSupport.resolveCustomSechubSeverity(result, run); + if (serecoSeverity == null) { + Level level = sarifSchema210LogicSupport.resolveLevel(result, run); + return mapToSeverity(level); + } + return serecoSeverity; } private class ResultData { diff --git a/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporterTest.java b/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporterTest.java index 000af5dca5..ab90d5f2e2 100644 --- a/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporterTest.java +++ b/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SarifV1JSONImporterTest.java @@ -36,6 +36,8 @@ class SarifV1JSONImporterTest { private static String sarif_2_1_0_gosec2_9_5_example5_cosdescan; private static String sarif_2_1_0_sarif_2_1_0_gitleaks_8_0; private static String sarif_2_1_0_sarif_2_1_0_gitleaks_8_0_one_finding_with_revision_id; + private static String sarif_2_1_0_gitleaks_8_0_with_validator_severity_properties; + private static String sarif_2_1_0_gitleaks_8_0_with_invalid_validator_severity_properties; private SarifV1JSONImporter importerToTest; @@ -51,6 +53,9 @@ public static void before() { sarif_2_1_0_owasp_zap = loadSarifTestFile("sarif_2.1.0_owasp_zap.json"); sarif_2_1_0_sarif_2_1_0_gitleaks_8_0 = loadSarifTestFile("sarif_2.1.0_gitleaks_8.0.json"); sarif_2_1_0_sarif_2_1_0_gitleaks_8_0_one_finding_with_revision_id = loadSarifTestFile("sarif_2.1.0_gitleaks_8.0-one-finding-with-revision.json"); + sarif_2_1_0_gitleaks_8_0_with_validator_severity_properties = loadSarifTestFile("sarif_2.1.0_gitleaks_8.0-with-validator-severity-properties.json"); + sarif_2_1_0_gitleaks_8_0_with_invalid_validator_severity_properties = loadSarifTestFile( + "sarif_2.1.0_gitleaks_8.0-with-invalid-validator-severity-properties.json"); } @BeforeEach @@ -319,6 +324,86 @@ void gitleaks_8_0_simple_example_secretscan__can_be_imported_and_revision_inform /* @formatter:on */ } + @Test + void sarif_2_1_0_gitleaks_8_0_with_validator_severity_properties__can_be_imported_and_severities_are_available() throws Exception { + /* prepare */ + importerToTest.workaroundSupport.workarounds.add(new GitleaksSarifImportWorkaround()); + SerecoMetaData result = importerToTest.importResult(sarif_2_1_0_gitleaks_8_0_with_validator_severity_properties, ScanType.SECRET_SCAN); + + /* execute */ + List vulnerabilities = result.getVulnerabilities(); + + /* test */ + /* @formatter:off */ + assertVulnerabilities(vulnerabilities). + hasVulnerabilities(6). + verifyVulnerability(). + withSeverity(SerecoSeverity.INFO). + withCodeLocation("UnSAFE_Bank/Backend/docker-compose.yml", 12, 14). + containingSource("531486b2bf646636a6a1bba61e78ec4a4a54efbd"). + done(). + isContained(). + verifyVulnerability(). + withSeverity(SerecoSeverity.UNCLASSIFIED). + withCodeLocation("UnSAFE_Bank/Backend/src/api/application/config/database.php", 80, 7). + containingSource("531486b2bf646636a6a1bba61e78ec4a4a54efbd"). + done(). + isContained(). + verifyVulnerability(). + withSeverity(SerecoSeverity.LOW). + withCodeLocation("UnSAFE_Bank/Backend/web/src/app/thunks/Authentication/ForgotPassword/handleForgotPasswordGetOTPThunk.tsx", 32, 14). + containingSource("9bbc0d79e686e847bc305c9bd4cc2ea6"). + done(). + isContained(). + verifyVulnerability(). + withSeverity(SerecoSeverity.MEDIUM). + withCodeLocation("UnSAFE_Bank/Backend/web/src/app/thunks/OTP/handleGetOTPThunk.tsx", 31, 14). + containingSource("9bbc0d79e686e847bc305c9bd4cc2ea6"). + done(). + isContained(). + verifyVulnerability(). + withSeverity(SerecoSeverity.HIGH). + withCodeLocation("UnSAFE_Bank/iOS/Source Code/Podfile.lock", 23, 4). + containingSource("b3816fddcf28aa29d94b10ec305cd52be14c472b"). + done(). + isContained(). + verifyVulnerability(). + withSeverity(SerecoSeverity.CRITICAL). + withCodeLocation("UnSAFE_Bank/iOS/Source Code/Pods/Manifest.lock", 23, 4). + containingSource("b3816fddcf28aa29d94b10ec305cd52be14c472b"). + done(). + isContained(); + /* @formatter:on */ + } + + @Test + void sarif_2_1_0_gitleaks_8_0_with_invalid_validator_severity_properties__can_be_imported_and_severities_are_set_to_sarif_default() throws Exception { + /* prepare */ + importerToTest.workaroundSupport.workarounds.add(new GitleaksSarifImportWorkaround()); + SerecoMetaData result = importerToTest.importResult(sarif_2_1_0_gitleaks_8_0_with_invalid_validator_severity_properties, ScanType.SECRET_SCAN); + + /* execute */ + List vulnerabilities = result.getVulnerabilities(); + + /* test */ + /* @formatter:off */ + assertVulnerabilities(vulnerabilities). + hasVulnerabilities(2). + verifyVulnerability(). + withSeverity(SerecoSeverity.MEDIUM). + withCodeLocation("UnSAFE_Bank/Backend/docker-compose.yml", 12, 14). + containingSource("531486b2bf646636a6a1bba61e78ec4a4a54efbd"). + done(). + isContained(). + verifyVulnerability(). + withSeverity(SerecoSeverity.MEDIUM). + withCodeLocation("UnSAFE_Bank/Backend/src/api/application/config/database.php", 80, 7). + containingSource("531486b2bf646636a6a1bba61e78ec4a4a54efbd"). + done(). + isContained(); + /* @formatter:on */ + } + @Test void brakeman_sarif_report_can_be_imported() { /* prepare */ diff --git a/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SensitiveDataMaskingServiceTest.java b/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SensitiveDataMaskingServiceTest.java index e04f24478e..5bd333b742 100644 --- a/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SensitiveDataMaskingServiceTest.java +++ b/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/importer/SensitiveDataMaskingServiceTest.java @@ -214,7 +214,7 @@ private SecHubConfigurationModel createConfigWithOutSensitive() { private SecHubConfigurationModel createConfiguration(boolean withSensitiveHeaders) { String path = withSensitiveHeaders ? SECHUB_WEBSCAN_CONFIG_FILE_WITH_SENSITIVE_HEADERS : SECHUB_WEBSCAN_CONFIG_FILE_WITHOUT_SENSITIVE_HEADERS; - String sechubConfigJson = TestFileReader.loadTextFile(path); + String sechubConfigJson = TestFileReader.readTextFromFile(path); SecHubConfigurationModel result = SecHubConfiguration.createFromJSON(sechubConfigJson); return result; } diff --git a/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/metadata/SeverityTest.java b/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/metadata/SerecoSeverityTest.java similarity index 97% rename from sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/metadata/SeverityTest.java rename to sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/metadata/SerecoSeverityTest.java index 5593f390d4..5e6268cda4 100644 --- a/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/metadata/SeverityTest.java +++ b/sechub-sereco/src/test/java/com/mercedesbenz/sechub/sereco/metadata/SerecoSeverityTest.java @@ -5,7 +5,7 @@ import org.junit.Test; -public class SeverityTest { +public class SerecoSeverityTest { @Test public void value_null_is_null() { diff --git a/sechub-sereco/src/test/resources/sarif/sarif_2.1.0_gitleaks_8.0-with-invalid-validator-severity-properties.json b/sechub-sereco/src/test/resources/sarif/sarif_2.1.0_gitleaks_8.0-with-invalid-validator-severity-properties.json new file mode 100644 index 0000000000..4d77229fce --- /dev/null +++ b/sechub-sereco/src/test/resources/sarif/sarif_2.1.0_gitleaks_8.0-with-invalid-validator-severity-properties.json @@ -0,0 +1,850 @@ +{ + "$schema" : "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version" : "2.1.0", + "runs" : [ { + "tool" : { + "driver" : { + "name" : "gitleaks", + "semanticVersion" : "v8.0.0", + "rules" : [ { + "id" : "adafruit-api-key", + "name" : "Adafruit API Key", + "shortDescription" : { + "text" : "(?i)(?:adafruit)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "adobe-client-id", + "name" : "Adobe Client ID (Oauth Web)", + "shortDescription" : { + "text" : "(?i)(?:adobe)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "adobe-client-secret", + "name" : "Adobe Client Secret", + "shortDescription" : { + "text" : "(?i)\\b((p8e-)(?i)[a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "age secret key", + "name" : "Age secret key", + "shortDescription" : { + "text" : "AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}" + } + }, { + "id" : "airtable-api-key", + "name" : "Airtable API Key", + "shortDescription" : { + "text" : "(?i)(?:airtable)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{17})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "algolia-api-key", + "name" : "Algolia API Key", + "shortDescription" : { + "text" : "(?i)(?:algolia)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "alibaba-access-key-id", + "name" : "Alibaba AccessKey ID", + "shortDescription" : { + "text" : "(?i)\\b((LTAI)(?i)[a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "alibaba-secret-key", + "name" : "Alibaba Secret Key", + "shortDescription" : { + "text" : "(?i)(?:alibaba)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "asana-client-id", + "name" : "Asana Client ID", + "shortDescription" : { + "text" : "(?i)(?:asana)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "asana-client-secret", + "name" : "Asana Client Secret", + "shortDescription" : { + "text" : "(?i)(?:asana)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "atlassian-api-token", + "name" : "Atlassian API token", + "shortDescription" : { + "text" : "(?i)(?:atlassian|confluence|jira)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "aws-access-token", + "name" : "AWS", + "shortDescription" : { + "text" : "(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}" + } + }, { + "id" : "bitbucket-client-id", + "name" : "BitBucket Client ID", + "shortDescription" : { + "text" : "(?i)(?:bitbucket)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bitbucket-client-secret", + "name" : "BitBucket Client Secret", + "shortDescription" : { + "text" : "(?i)(?:bitbucket)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bittrex-access-key", + "name" : "Bittrex Access Key", + "shortDescription" : { + "text" : "(?i)(?:bittrex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bittrex-secret-key", + "name" : "Bittrex Secret Key", + "shortDescription" : { + "text" : "(?i)(?:bittrex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "beamer-api-token", + "name" : "Beamer API token", + "shortDescription" : { + "text" : "(?i)(?:beamer)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(b_[a-z0-9=_\\-]{44})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "codecov-access-token", + "name" : "Codecov Access Token", + "shortDescription" : { + "text" : "(?i)(?:codecov)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "coinbase-access-token", + "name" : "Coinbase Access Token", + "shortDescription" : { + "text" : "(?i)(?:coinbase)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "clojars-api-token", + "name" : "Clojars API token", + "shortDescription" : { + "text" : "(?i)(CLOJARS_)[a-z0-9]{60}" + } + }, { + "id" : "confluent-access-token", + "name" : "Confluent Access Token", + "shortDescription" : { + "text" : "(?i)(?:confluent)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "confluent-secret-key", + "name" : "Confluent Secret Key", + "shortDescription" : { + "text" : "(?i)(?:confluent)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "contentful-delivery-api-token", + "name" : "Contentful delivery API token", + "shortDescription" : { + "text" : "(?i)(?:contentful)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{43})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "databricks-api-token", + "name" : "Databricks API token", + "shortDescription" : { + "text" : "(?i)\\b(dapi[a-h0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "datadog-access-token", + "name" : "Datadog Access Token", + "shortDescription" : { + "text" : "(?i)(?:datadog)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-api-token", + "name" : "Discord API key", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-client-id", + "name" : "Discord client ID", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{18})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-client-secret", + "name" : "Discord client secret", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "doppler-api-token", + "name" : "Doppler API token", + "shortDescription" : { + "text" : "(dp\\.pt\\.)(?i)[a-z0-9]{43}" + } + }, { + "id" : "dropbox-api-token", + "name" : "Dropbox API secret", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{15})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "dropbox-long-lived-api-token", + "name" : "Dropbox long lived API token", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\\-_=]{43})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "dropbox-short-lived-api-token", + "name" : "Dropbox short lived API token", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(sl\\.[a-z0-9\\-=_]{135})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "droneci-access-token", + "name" : "Droneci Access Token", + "shortDescription" : { + "text" : "(?i)(?:droneci)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "duffel-api-token", + "name" : "Duffel API token", + "shortDescription" : { + "text" : "duffel_(test|live)_(?i)[a-z0-9_\\-=]{43}" + } + }, { + "id" : "dynatrace-api-token", + "name" : "Dynatrace API token", + "shortDescription" : { + "text" : "dt0c01\\.(?i)[a-z0-9]{24}\\.[a-z0-9]{64}" + } + }, { + "id" : "easypost-api-token", + "name" : "EasyPost API token", + "shortDescription" : { + "text" : "EZAK(?i)[a-z0-9]{54}" + } + }, { + "id" : "easypost-test-api-token", + "name" : "EasyPost test API token", + "shortDescription" : { + "text" : "EZTK(?i)[a-z0-9]{54}" + } + }, { + "id" : "etsy-access-token", + "name" : "Etsy Access Token", + "shortDescription" : { + "text" : "(?i)(?:etsy)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "facebook", + "name" : "facebook", + "shortDescription" : { + "text" : "(?i)(?:facebook)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "fastly-api-token", + "name" : "Fastly API key", + "shortDescription" : { + "text" : "(?i)(?:fastly)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finicity-client-secret", + "name" : "Finicity Client Secret", + "shortDescription" : { + "text" : "(?i)(?:finicity)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finicity-api-token", + "name" : "Finicity API token", + "shortDescription" : { + "text" : "(?i)(?:finicity)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "flickr-access-token", + "name" : "Flickr Access Token", + "shortDescription" : { + "text" : "(?i)(?:flickr)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finnhub-access-token", + "name" : "Finnhub Access Token", + "shortDescription" : { + "text" : "(?i)(?:finnhub)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "flutterwave-public-key", + "name" : "Finicity Public Key", + "shortDescription" : { + "text" : "FLWPUBK_TEST-(?i)[a-h0-9]{32}-X" + } + }, { + "id" : "flutterwave-secret-key", + "name" : "Flutterwave Secret Key", + "shortDescription" : { + "text" : "FLWSECK_TEST-(?i)[a-h0-9]{32}-X" + } + }, { + "id" : "flutterwave-encryption-key", + "name" : "Flutterwave Encryption Key", + "shortDescription" : { + "text" : "FLWSECK_TEST-(?i)[a-h0-9]{12}" + } + }, { + "id" : "frameio-api-token", + "name" : "Frame.io API token", + "shortDescription" : { + "text" : "fio-u-(?i)[a-z0-9\\-_=]{64}" + } + }, { + "id" : "freshbooks-access-token", + "name" : "Freshbooks Access Token", + "shortDescription" : { + "text" : "(?i)(?:freshbooks)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "gocardless-api-token", + "name" : "GoCardless API token", + "shortDescription" : { + "text" : "(?i)(?:gocardless)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(live_(?i)[a-z0-9\\-_=]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "gcp-api-key", + "name" : "GCP API key", + "shortDescription" : { + "text" : "(?i)\\b(AIza[0-9A-Za-z\\\\-_]{35})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "github-pat", + "name" : "GitHub Personal Access Token", + "shortDescription" : { + "text" : "ghp_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-oauth", + "name" : "GitHub OAuth Access Token", + "shortDescription" : { + "text" : "gho_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-app-token", + "name" : "GitHub App Token", + "shortDescription" : { + "text" : "(ghu|ghs)_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-refresh-token", + "name" : "GitHub Refresh Token", + "shortDescription" : { + "text" : "ghr_[0-9a-zA-Z]{36}" + } + }, { + "id" : "gitlab-pat", + "name" : "Gitlab Personal Access Token", + "shortDescription" : { + "text" : "glpat-[0-9a-zA-Z\\-\\_]{20}" + } + }, { + "id" : "gitter-access-token", + "name" : "Gitter Access Token", + "shortDescription" : { + "text" : "(?i)(?:gitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "hashicorp-tf-api-token", + "name" : "HashiCorp Terraform user/org API token", + "shortDescription" : { + "text" : "(?i)[a-z0-9]{14}\\.atlasv1\\.[a-z0-9\\-_=]{60,70}" + } + }, { + "id" : "heroku-api-key", + "name" : "Heroku API Key", + "shortDescription" : { + "text" : "(?i)(?:heroku)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "hubspot-api-key", + "name" : "HubSpot API Token", + "shortDescription" : { + "text" : "(?i)(?:hubspot)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "intercom-api-key", + "name" : "Intercom API Token", + "shortDescription" : { + "text" : "(?i)(?:intercom)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{60})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kraken-access-token", + "name" : "Kraken Access Token", + "shortDescription" : { + "text" : "(?i)(?:kraken)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9\\/=_\\+\\-]{80,90})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kucoin-access-token", + "name" : "Kucoin Access Token", + "shortDescription" : { + "text" : "(?i)(?:kucoin)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kucoin-secret-key", + "name" : "Kucoin Secret Key", + "shortDescription" : { + "text" : "(?i)(?:kucoin)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "launchdarkly-access-token", + "name" : "Launchdarkly Access Token", + "shortDescription" : { + "text" : "(?i)(?:launchdarkly)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linear-api-key", + "name" : "Linear API Token", + "shortDescription" : { + "text" : "lin_api_(?i)[a-z0-9]{40}" + } + }, { + "id" : "linear-client-secret", + "name" : "Linear Client Secret", + "shortDescription" : { + "text" : "(?i)(?:linear)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linkedin-client-id", + "name" : "LinkedIn Client ID", + "shortDescription" : { + "text" : "(?i)(?:linkedin|linked-in)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{14})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linkedin-client-secret", + "name" : "LinkedIn Client secret", + "shortDescription" : { + "text" : "(?i)(?:linkedin|linked-in)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "lob-api-key", + "name" : "Lob API Key", + "shortDescription" : { + "text" : "(?i)(?:lob)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}((live|test)_[a-f0-9]{35})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "lob-pub-api-key", + "name" : "Lob Publishable API Key", + "shortDescription" : { + "text" : "(?i)(?:lob)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}((test|live)_pub_[a-f0-9]{31})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailchimp-api-key", + "name" : "Mailchimp API key", + "shortDescription" : { + "text" : "(?i)(?:mailchimp)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32}-us20)(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-pub-key", + "name" : "Mailgun public validation key", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(pubkey-[a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-private-api-token", + "name" : "Mailgun private API token", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(key-[a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-signing-key", + "name" : "Mailgun webhook signing key", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mapbox-api-token", + "name" : "MapBox API token", + "shortDescription" : { + "text" : "(?i)(?:mapbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(pk\\.[a-z0-9]{60}\\.[a-z0-9]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mattermost-access-token", + "name" : "Mattermost Access Token", + "shortDescription" : { + "text" : "(?i)(?:mattermost)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{26})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "messagebird-api-token", + "name" : "MessageBird API token", + "shortDescription" : { + "text" : "(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{25})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "messagebird-client-id", + "name" : "MessageBird client ID", + "shortDescription" : { + "text" : "(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "netlify-access-token", + "name" : "Netlify Access Token", + "shortDescription" : { + "text" : "(?i)(?:netlify)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{40,46})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-user-api-key", + "name" : "New Relic user API Key", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(NRAK-[a-z0-9]{27})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-user-api-id", + "name" : "New Relic user API ID", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-browser-api-token", + "name" : "New Relic ingest browser API token", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(NRJS-[a-f0-9]{19})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "npm-access-token", + "name" : "npm access token", + "shortDescription" : { + "text" : "(?i)\\b(npm_[a-z0-9]{36})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "nytimes-access-token", + "name" : "Nytimes Access Token", + "shortDescription" : { + "text" : "(?i)(?:nytimes|new-york-times,|newyorktimes)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "okta-access-token", + "name" : "Okta Access Token", + "shortDescription" : { + "text" : "(?i)(?:okta)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{42})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-client-id", + "name" : "Plaid Client ID", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-secret-key", + "name" : "Plaid Secret key", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-api-token", + "name" : "Plaid API Token", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(access-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-password", + "name" : "PlanetScale password", + "shortDescription" : { + "text" : "(?i)\\b(pscale_pw_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-api-token", + "name" : "PlanetScale API token", + "shortDescription" : { + "text" : "(?i)\\b(pscale_tkn_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-oauth-token", + "name" : "PlanetScale OAuth token", + "shortDescription" : { + "text" : "(?i)\\b(pscale_oauth_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "postman-api-token", + "name" : "Postman API token", + "shortDescription" : { + "text" : "(?i)\\b(PMAK-(?i)[a-f0-9]{24}\\-[a-f0-9]{34})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "private-key", + "name" : "Private Key", + "shortDescription" : { + "text" : "(?i)-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY-----[\\s\\S-]*KEY----" + } + }, { + "id" : "pulumi-api-token", + "name" : "Pulumi API token", + "shortDescription" : { + "text" : "(?i)\\b(pul-[a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "pypi-upload-token", + "name" : "PyPI upload token", + "shortDescription" : { + "text" : "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\\-_]{50,1000}" + } + }, { + "id" : "rubygems-api-token", + "name" : "Rubygem API token", + "shortDescription" : { + "text" : "(?i)\\b(rubygems_[a-f0-9]{48})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "rapidapi-access-token", + "name" : "RapidAPI Access Token", + "shortDescription" : { + "text" : "(?i)(?:rapidapi)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{50})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendbird-access-id", + "name" : "Sendbird Access ID", + "shortDescription" : { + "text" : "(?i)(?:sendbird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendbird-access-token", + "name" : "Sendbird Access Token", + "shortDescription" : { + "text" : "(?i)(?:sendbird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendgrid-api-token", + "name" : "SendGrid API token", + "shortDescription" : { + "text" : "(?i)\\b(SG\\.(?i)[a-z0-9=_\\-\\.]{66})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendinblue-api-token", + "name" : "Sendinblue API token", + "shortDescription" : { + "text" : "(?i)\\b(xkeysib-[a-f0-9]{64}\\-(?i)[a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sentry-access-token", + "name" : "Sentry Access Token", + "shortDescription" : { + "text" : "(?i)(?:sentry)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "shippo-api-token", + "name" : "Shippo API token", + "shortDescription" : { + "text" : "(?i)\\b(shippo_(live|test)_[a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "shopify-access-token", + "name" : "Shopify access token", + "shortDescription" : { + "text" : "shpat_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-custom-access-token", + "name" : "Shopify custom access token", + "shortDescription" : { + "text" : "shpca_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-private-app-access-token", + "name" : "Shopify private app access token", + "shortDescription" : { + "text" : "shppa_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-shared-secret", + "name" : "Shopify shared secret", + "shortDescription" : { + "text" : "shpss_[a-fA-F0-9]{32}" + } + }, { + "id" : "slack-access-token", + "name" : "Slack token", + "shortDescription" : { + "text" : "xox[baprs]-([0-9a-zA-Z]{10,48})" + } + }, { + "id" : "slack-web-hook", + "name" : "Slack Webhook", + "shortDescription" : { + "text" : "https:\\/\\/hooks.slack.com\\/services\\/[A-Za-z0-9+\\/]{44,46}" + } + }, { + "id" : "stripe-access-token", + "name" : "Stripe", + "shortDescription" : { + "text" : "(?i)(sk|pk)_(test|live)_[0-9a-z]{10,32}" + } + }, { + "id" : "square-access-token", + "name" : "Square Access Token", + "shortDescription" : { + "text" : "(?i)\\b(sq0atp-[0-9A-Za-z\\-_]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "squarespace-access-token", + "name" : "Squarespace Access Token", + "shortDescription" : { + "text" : "(?i)(?:squarespace)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sumologic-access-id", + "name" : "SumoLogic Access ID", + "shortDescription" : { + "text" : "(?i)(?:sumo)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{14})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sumologic-access-token", + "name" : "SumoLogic Access Token", + "shortDescription" : { + "text" : "(?i)(?:sumo)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "travisci-access-token", + "name" : "Travis CI Access Token", + "shortDescription" : { + "text" : "(?i)(?:travis)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twilio-api-key", + "name" : "Twilio API Key", + "shortDescription" : { + "text" : "SK[0-9a-fA-F]{32}" + } + }, { + "id" : "twitch-api-token", + "name" : "Twitch API token", + "shortDescription" : { + "text" : "(?i)(?:twitch)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-api-key", + "name" : "Twitter API Key", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{25})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-api-secret", + "name" : "Twitter API Secret", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{50})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-access-token", + "name" : "Twitter Access Token", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{15,25}-[a-zA-Z0-9]{20,40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-access-secret", + "name" : "Twitter Access Secret", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{45})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-bearer-token", + "name" : "Twitter Bearer Token", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(A{22}[a-zA-Z0-9%]{80,100})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "typeform-api-token", + "name" : "Typeform API token", + "shortDescription" : { + "text" : "(?i)(?:typeform)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(tfp_[a-z0-9\\-_\\.=]{59})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-api-key", + "name" : "Yandex API Key", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(AQVN[A-Za-z0-9_\\-]{35,38})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-aws-access-token", + "name" : "Yandex AWS Access Token", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(YC[a-zA-Z0-9_\\-]{38})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-access-token", + "name" : "Yandex Access Token", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(t1\\.[A-Z0-9a-z_-]+[=]{0,2}\\.[A-Z0-9a-z_-]{86}[=]{0,2})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "zendesk-secret-key", + "name" : "Zendesk Secret Key", + "shortDescription" : { + "text" : "(?i)(?:zendesk)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "generic-api-key", + "name" : "Generic API Key", + "shortDescription" : { + "text" : "(?i)(?:key|api|token|secret|client|passwd|password|auth)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-z\\-_.=]{10,150})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + } ] + } + }, + "results" : [ { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/docker-compose.yml." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/docker-compose.yml" + }, + "region" : { + "startLine" : 12, + "startColumn" : 14, + "endLine" : 13, + "endColumn" : 1, + "snippet" : { + "text" : "531486b2bf646636a6a1bba61e78ec4a4a54efbd" + }, + "properties" : { + "secretscan.sereco.severity" : "invalid-value" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/src/api/application/config/database.php." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/src/api/application/config/database.php" + }, + "region" : { + "startLine" : 80, + "startColumn" : 7, + "endLine" : 80, + "endColumn" : 61, + "snippet" : { + "text" : "531486b2bf646636a6a1bba61e78ec4a4a54efbd" + }, + "properties" : { + "secretscan.sereco.severity" : "another-invalid-value-1234" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + } ] + } ] +} \ No newline at end of file diff --git a/sechub-sereco/src/test/resources/sarif/sarif_2.1.0_gitleaks_8.0-with-validator-severity-properties.json b/sechub-sereco/src/test/resources/sarif/sarif_2.1.0_gitleaks_8.0-with-validator-severity-properties.json new file mode 100644 index 0000000000..346bf7be33 --- /dev/null +++ b/sechub-sereco/src/test/resources/sarif/sarif_2.1.0_gitleaks_8.0-with-validator-severity-properties.json @@ -0,0 +1,974 @@ +{ + "$schema" : "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version" : "2.1.0", + "runs" : [ { + "tool" : { + "driver" : { + "name" : "gitleaks", + "semanticVersion" : "v8.0.0", + "rules" : [ { + "id" : "adafruit-api-key", + "name" : "Adafruit API Key", + "shortDescription" : { + "text" : "(?i)(?:adafruit)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "adobe-client-id", + "name" : "Adobe Client ID (Oauth Web)", + "shortDescription" : { + "text" : "(?i)(?:adobe)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "adobe-client-secret", + "name" : "Adobe Client Secret", + "shortDescription" : { + "text" : "(?i)\\b((p8e-)(?i)[a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "age secret key", + "name" : "Age secret key", + "shortDescription" : { + "text" : "AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}" + } + }, { + "id" : "airtable-api-key", + "name" : "Airtable API Key", + "shortDescription" : { + "text" : "(?i)(?:airtable)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{17})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "algolia-api-key", + "name" : "Algolia API Key", + "shortDescription" : { + "text" : "(?i)(?:algolia)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "alibaba-access-key-id", + "name" : "Alibaba AccessKey ID", + "shortDescription" : { + "text" : "(?i)\\b((LTAI)(?i)[a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "alibaba-secret-key", + "name" : "Alibaba Secret Key", + "shortDescription" : { + "text" : "(?i)(?:alibaba)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "asana-client-id", + "name" : "Asana Client ID", + "shortDescription" : { + "text" : "(?i)(?:asana)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "asana-client-secret", + "name" : "Asana Client Secret", + "shortDescription" : { + "text" : "(?i)(?:asana)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "atlassian-api-token", + "name" : "Atlassian API token", + "shortDescription" : { + "text" : "(?i)(?:atlassian|confluence|jira)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "aws-access-token", + "name" : "AWS", + "shortDescription" : { + "text" : "(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}" + } + }, { + "id" : "bitbucket-client-id", + "name" : "BitBucket Client ID", + "shortDescription" : { + "text" : "(?i)(?:bitbucket)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bitbucket-client-secret", + "name" : "BitBucket Client Secret", + "shortDescription" : { + "text" : "(?i)(?:bitbucket)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bittrex-access-key", + "name" : "Bittrex Access Key", + "shortDescription" : { + "text" : "(?i)(?:bittrex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bittrex-secret-key", + "name" : "Bittrex Secret Key", + "shortDescription" : { + "text" : "(?i)(?:bittrex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "beamer-api-token", + "name" : "Beamer API token", + "shortDescription" : { + "text" : "(?i)(?:beamer)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(b_[a-z0-9=_\\-]{44})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "codecov-access-token", + "name" : "Codecov Access Token", + "shortDescription" : { + "text" : "(?i)(?:codecov)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "coinbase-access-token", + "name" : "Coinbase Access Token", + "shortDescription" : { + "text" : "(?i)(?:coinbase)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "clojars-api-token", + "name" : "Clojars API token", + "shortDescription" : { + "text" : "(?i)(CLOJARS_)[a-z0-9]{60}" + } + }, { + "id" : "confluent-access-token", + "name" : "Confluent Access Token", + "shortDescription" : { + "text" : "(?i)(?:confluent)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "confluent-secret-key", + "name" : "Confluent Secret Key", + "shortDescription" : { + "text" : "(?i)(?:confluent)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "contentful-delivery-api-token", + "name" : "Contentful delivery API token", + "shortDescription" : { + "text" : "(?i)(?:contentful)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{43})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "databricks-api-token", + "name" : "Databricks API token", + "shortDescription" : { + "text" : "(?i)\\b(dapi[a-h0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "datadog-access-token", + "name" : "Datadog Access Token", + "shortDescription" : { + "text" : "(?i)(?:datadog)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-api-token", + "name" : "Discord API key", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-client-id", + "name" : "Discord client ID", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{18})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-client-secret", + "name" : "Discord client secret", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "doppler-api-token", + "name" : "Doppler API token", + "shortDescription" : { + "text" : "(dp\\.pt\\.)(?i)[a-z0-9]{43}" + } + }, { + "id" : "dropbox-api-token", + "name" : "Dropbox API secret", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{15})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "dropbox-long-lived-api-token", + "name" : "Dropbox long lived API token", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\\-_=]{43})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "dropbox-short-lived-api-token", + "name" : "Dropbox short lived API token", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(sl\\.[a-z0-9\\-=_]{135})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "droneci-access-token", + "name" : "Droneci Access Token", + "shortDescription" : { + "text" : "(?i)(?:droneci)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "duffel-api-token", + "name" : "Duffel API token", + "shortDescription" : { + "text" : "duffel_(test|live)_(?i)[a-z0-9_\\-=]{43}" + } + }, { + "id" : "dynatrace-api-token", + "name" : "Dynatrace API token", + "shortDescription" : { + "text" : "dt0c01\\.(?i)[a-z0-9]{24}\\.[a-z0-9]{64}" + } + }, { + "id" : "easypost-api-token", + "name" : "EasyPost API token", + "shortDescription" : { + "text" : "EZAK(?i)[a-z0-9]{54}" + } + }, { + "id" : "easypost-test-api-token", + "name" : "EasyPost test API token", + "shortDescription" : { + "text" : "EZTK(?i)[a-z0-9]{54}" + } + }, { + "id" : "etsy-access-token", + "name" : "Etsy Access Token", + "shortDescription" : { + "text" : "(?i)(?:etsy)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "facebook", + "name" : "facebook", + "shortDescription" : { + "text" : "(?i)(?:facebook)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "fastly-api-token", + "name" : "Fastly API key", + "shortDescription" : { + "text" : "(?i)(?:fastly)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finicity-client-secret", + "name" : "Finicity Client Secret", + "shortDescription" : { + "text" : "(?i)(?:finicity)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finicity-api-token", + "name" : "Finicity API token", + "shortDescription" : { + "text" : "(?i)(?:finicity)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "flickr-access-token", + "name" : "Flickr Access Token", + "shortDescription" : { + "text" : "(?i)(?:flickr)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finnhub-access-token", + "name" : "Finnhub Access Token", + "shortDescription" : { + "text" : "(?i)(?:finnhub)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "flutterwave-public-key", + "name" : "Finicity Public Key", + "shortDescription" : { + "text" : "FLWPUBK_TEST-(?i)[a-h0-9]{32}-X" + } + }, { + "id" : "flutterwave-secret-key", + "name" : "Flutterwave Secret Key", + "shortDescription" : { + "text" : "FLWSECK_TEST-(?i)[a-h0-9]{32}-X" + } + }, { + "id" : "flutterwave-encryption-key", + "name" : "Flutterwave Encryption Key", + "shortDescription" : { + "text" : "FLWSECK_TEST-(?i)[a-h0-9]{12}" + } + }, { + "id" : "frameio-api-token", + "name" : "Frame.io API token", + "shortDescription" : { + "text" : "fio-u-(?i)[a-z0-9\\-_=]{64}" + } + }, { + "id" : "freshbooks-access-token", + "name" : "Freshbooks Access Token", + "shortDescription" : { + "text" : "(?i)(?:freshbooks)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "gocardless-api-token", + "name" : "GoCardless API token", + "shortDescription" : { + "text" : "(?i)(?:gocardless)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(live_(?i)[a-z0-9\\-_=]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "gcp-api-key", + "name" : "GCP API key", + "shortDescription" : { + "text" : "(?i)\\b(AIza[0-9A-Za-z\\\\-_]{35})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "github-pat", + "name" : "GitHub Personal Access Token", + "shortDescription" : { + "text" : "ghp_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-oauth", + "name" : "GitHub OAuth Access Token", + "shortDescription" : { + "text" : "gho_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-app-token", + "name" : "GitHub App Token", + "shortDescription" : { + "text" : "(ghu|ghs)_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-refresh-token", + "name" : "GitHub Refresh Token", + "shortDescription" : { + "text" : "ghr_[0-9a-zA-Z]{36}" + } + }, { + "id" : "gitlab-pat", + "name" : "Gitlab Personal Access Token", + "shortDescription" : { + "text" : "glpat-[0-9a-zA-Z\\-\\_]{20}" + } + }, { + "id" : "gitter-access-token", + "name" : "Gitter Access Token", + "shortDescription" : { + "text" : "(?i)(?:gitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "hashicorp-tf-api-token", + "name" : "HashiCorp Terraform user/org API token", + "shortDescription" : { + "text" : "(?i)[a-z0-9]{14}\\.atlasv1\\.[a-z0-9\\-_=]{60,70}" + } + }, { + "id" : "heroku-api-key", + "name" : "Heroku API Key", + "shortDescription" : { + "text" : "(?i)(?:heroku)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "hubspot-api-key", + "name" : "HubSpot API Token", + "shortDescription" : { + "text" : "(?i)(?:hubspot)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "intercom-api-key", + "name" : "Intercom API Token", + "shortDescription" : { + "text" : "(?i)(?:intercom)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{60})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kraken-access-token", + "name" : "Kraken Access Token", + "shortDescription" : { + "text" : "(?i)(?:kraken)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9\\/=_\\+\\-]{80,90})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kucoin-access-token", + "name" : "Kucoin Access Token", + "shortDescription" : { + "text" : "(?i)(?:kucoin)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kucoin-secret-key", + "name" : "Kucoin Secret Key", + "shortDescription" : { + "text" : "(?i)(?:kucoin)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "launchdarkly-access-token", + "name" : "Launchdarkly Access Token", + "shortDescription" : { + "text" : "(?i)(?:launchdarkly)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linear-api-key", + "name" : "Linear API Token", + "shortDescription" : { + "text" : "lin_api_(?i)[a-z0-9]{40}" + } + }, { + "id" : "linear-client-secret", + "name" : "Linear Client Secret", + "shortDescription" : { + "text" : "(?i)(?:linear)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linkedin-client-id", + "name" : "LinkedIn Client ID", + "shortDescription" : { + "text" : "(?i)(?:linkedin|linked-in)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{14})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linkedin-client-secret", + "name" : "LinkedIn Client secret", + "shortDescription" : { + "text" : "(?i)(?:linkedin|linked-in)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "lob-api-key", + "name" : "Lob API Key", + "shortDescription" : { + "text" : "(?i)(?:lob)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}((live|test)_[a-f0-9]{35})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "lob-pub-api-key", + "name" : "Lob Publishable API Key", + "shortDescription" : { + "text" : "(?i)(?:lob)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}((test|live)_pub_[a-f0-9]{31})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailchimp-api-key", + "name" : "Mailchimp API key", + "shortDescription" : { + "text" : "(?i)(?:mailchimp)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32}-us20)(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-pub-key", + "name" : "Mailgun public validation key", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(pubkey-[a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-private-api-token", + "name" : "Mailgun private API token", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(key-[a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-signing-key", + "name" : "Mailgun webhook signing key", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mapbox-api-token", + "name" : "MapBox API token", + "shortDescription" : { + "text" : "(?i)(?:mapbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(pk\\.[a-z0-9]{60}\\.[a-z0-9]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mattermost-access-token", + "name" : "Mattermost Access Token", + "shortDescription" : { + "text" : "(?i)(?:mattermost)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{26})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "messagebird-api-token", + "name" : "MessageBird API token", + "shortDescription" : { + "text" : "(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{25})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "messagebird-client-id", + "name" : "MessageBird client ID", + "shortDescription" : { + "text" : "(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "netlify-access-token", + "name" : "Netlify Access Token", + "shortDescription" : { + "text" : "(?i)(?:netlify)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{40,46})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-user-api-key", + "name" : "New Relic user API Key", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(NRAK-[a-z0-9]{27})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-user-api-id", + "name" : "New Relic user API ID", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-browser-api-token", + "name" : "New Relic ingest browser API token", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(NRJS-[a-f0-9]{19})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "npm-access-token", + "name" : "npm access token", + "shortDescription" : { + "text" : "(?i)\\b(npm_[a-z0-9]{36})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "nytimes-access-token", + "name" : "Nytimes Access Token", + "shortDescription" : { + "text" : "(?i)(?:nytimes|new-york-times,|newyorktimes)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "okta-access-token", + "name" : "Okta Access Token", + "shortDescription" : { + "text" : "(?i)(?:okta)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{42})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-client-id", + "name" : "Plaid Client ID", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-secret-key", + "name" : "Plaid Secret key", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-api-token", + "name" : "Plaid API Token", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(access-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-password", + "name" : "PlanetScale password", + "shortDescription" : { + "text" : "(?i)\\b(pscale_pw_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-api-token", + "name" : "PlanetScale API token", + "shortDescription" : { + "text" : "(?i)\\b(pscale_tkn_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-oauth-token", + "name" : "PlanetScale OAuth token", + "shortDescription" : { + "text" : "(?i)\\b(pscale_oauth_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "postman-api-token", + "name" : "Postman API token", + "shortDescription" : { + "text" : "(?i)\\b(PMAK-(?i)[a-f0-9]{24}\\-[a-f0-9]{34})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "private-key", + "name" : "Private Key", + "shortDescription" : { + "text" : "(?i)-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY-----[\\s\\S-]*KEY----" + } + }, { + "id" : "pulumi-api-token", + "name" : "Pulumi API token", + "shortDescription" : { + "text" : "(?i)\\b(pul-[a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "pypi-upload-token", + "name" : "PyPI upload token", + "shortDescription" : { + "text" : "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\\-_]{50,1000}" + } + }, { + "id" : "rubygems-api-token", + "name" : "Rubygem API token", + "shortDescription" : { + "text" : "(?i)\\b(rubygems_[a-f0-9]{48})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "rapidapi-access-token", + "name" : "RapidAPI Access Token", + "shortDescription" : { + "text" : "(?i)(?:rapidapi)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{50})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendbird-access-id", + "name" : "Sendbird Access ID", + "shortDescription" : { + "text" : "(?i)(?:sendbird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendbird-access-token", + "name" : "Sendbird Access Token", + "shortDescription" : { + "text" : "(?i)(?:sendbird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendgrid-api-token", + "name" : "SendGrid API token", + "shortDescription" : { + "text" : "(?i)\\b(SG\\.(?i)[a-z0-9=_\\-\\.]{66})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendinblue-api-token", + "name" : "Sendinblue API token", + "shortDescription" : { + "text" : "(?i)\\b(xkeysib-[a-f0-9]{64}\\-(?i)[a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sentry-access-token", + "name" : "Sentry Access Token", + "shortDescription" : { + "text" : "(?i)(?:sentry)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "shippo-api-token", + "name" : "Shippo API token", + "shortDescription" : { + "text" : "(?i)\\b(shippo_(live|test)_[a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "shopify-access-token", + "name" : "Shopify access token", + "shortDescription" : { + "text" : "shpat_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-custom-access-token", + "name" : "Shopify custom access token", + "shortDescription" : { + "text" : "shpca_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-private-app-access-token", + "name" : "Shopify private app access token", + "shortDescription" : { + "text" : "shppa_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-shared-secret", + "name" : "Shopify shared secret", + "shortDescription" : { + "text" : "shpss_[a-fA-F0-9]{32}" + } + }, { + "id" : "slack-access-token", + "name" : "Slack token", + "shortDescription" : { + "text" : "xox[baprs]-([0-9a-zA-Z]{10,48})" + } + }, { + "id" : "slack-web-hook", + "name" : "Slack Webhook", + "shortDescription" : { + "text" : "https:\\/\\/hooks.slack.com\\/services\\/[A-Za-z0-9+\\/]{44,46}" + } + }, { + "id" : "stripe-access-token", + "name" : "Stripe", + "shortDescription" : { + "text" : "(?i)(sk|pk)_(test|live)_[0-9a-z]{10,32}" + } + }, { + "id" : "square-access-token", + "name" : "Square Access Token", + "shortDescription" : { + "text" : "(?i)\\b(sq0atp-[0-9A-Za-z\\-_]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "squarespace-access-token", + "name" : "Squarespace Access Token", + "shortDescription" : { + "text" : "(?i)(?:squarespace)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sumologic-access-id", + "name" : "SumoLogic Access ID", + "shortDescription" : { + "text" : "(?i)(?:sumo)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{14})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sumologic-access-token", + "name" : "SumoLogic Access Token", + "shortDescription" : { + "text" : "(?i)(?:sumo)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "travisci-access-token", + "name" : "Travis CI Access Token", + "shortDescription" : { + "text" : "(?i)(?:travis)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twilio-api-key", + "name" : "Twilio API Key", + "shortDescription" : { + "text" : "SK[0-9a-fA-F]{32}" + } + }, { + "id" : "twitch-api-token", + "name" : "Twitch API token", + "shortDescription" : { + "text" : "(?i)(?:twitch)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-api-key", + "name" : "Twitter API Key", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{25})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-api-secret", + "name" : "Twitter API Secret", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{50})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-access-token", + "name" : "Twitter Access Token", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{15,25}-[a-zA-Z0-9]{20,40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-access-secret", + "name" : "Twitter Access Secret", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{45})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-bearer-token", + "name" : "Twitter Bearer Token", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(A{22}[a-zA-Z0-9%]{80,100})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "typeform-api-token", + "name" : "Typeform API token", + "shortDescription" : { + "text" : "(?i)(?:typeform)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(tfp_[a-z0-9\\-_\\.=]{59})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-api-key", + "name" : "Yandex API Key", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(AQVN[A-Za-z0-9_\\-]{35,38})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-aws-access-token", + "name" : "Yandex AWS Access Token", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(YC[a-zA-Z0-9_\\-]{38})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-access-token", + "name" : "Yandex Access Token", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(t1\\.[A-Z0-9a-z_-]+[=]{0,2}\\.[A-Z0-9a-z_-]{86}[=]{0,2})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "zendesk-secret-key", + "name" : "Zendesk Secret Key", + "shortDescription" : { + "text" : "(?i)(?:zendesk)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "generic-api-key", + "name" : "Generic API Key", + "shortDescription" : { + "text" : "(?i)(?:key|api|token|secret|client|passwd|password|auth)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-z\\-_.=]{10,150})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + } ] + } + }, + "results" : [ { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/docker-compose.yml." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/docker-compose.yml" + }, + "region" : { + "startLine" : 12, + "startColumn" : 14, + "endLine" : 13, + "endColumn" : 1, + "snippet" : { + "text" : "531486b2bf646636a6a1bba61e78ec4a4a54efbd" + }, + "properties" : { + "secretscan.sereco.severity" : "info" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/src/api/application/config/database.php." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/src/api/application/config/database.php" + }, + "region" : { + "startLine" : 80, + "startColumn" : 7, + "endLine" : 80, + "endColumn" : 61, + "snippet" : { + "text" : "531486b2bf646636a6a1bba61e78ec4a4a54efbd" + }, + "properties" : { + "secretscan.sereco.severity" : "unclassified" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/web/src/app/thunks/Authentication/ForgotPassword/handleForgotPasswordGetOTPThunk.tsx." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/web/src/app/thunks/Authentication/ForgotPassword/handleForgotPasswordGetOTPThunk.tsx" + }, + "region" : { + "startLine" : 32, + "startColumn" : 14, + "endLine" : 32, + "endColumn" : 56, + "snippet" : { + "text" : "9bbc0d79e686e847bc305c9bd4cc2ea6" + }, + "properties" : { + "secretscan.sereco.severity" : "low" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/web/src/app/thunks/OTP/handleGetOTPThunk.tsx." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/web/src/app/thunks/OTP/handleGetOTPThunk.tsx" + }, + "region" : { + "startLine" : 31, + "startColumn" : 14, + "endLine" : 31, + "endColumn" : 56, + "snippet" : { + "text" : "9bbc0d79e686e847bc305c9bd4cc2ea6" + }, + "properties" : { + "secretscan.sereco.severity" : "medium" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/iOS/Source Code/Podfile.lock." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/iOS/Source Code/Podfile.lock" + }, + "region" : { + "startLine" : 23, + "startColumn" : 4, + "endLine" : 24, + "endColumn" : 1, + "snippet" : { + "text" : "b3816fddcf28aa29d94b10ec305cd52be14c472b" + }, + "properties" : { + "secretscan.sereco.severity" : "high" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/iOS/Source Code/Pods/Manifest.lock." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/iOS/Source Code/Pods/Manifest.lock" + }, + "region" : { + "startLine" : 23, + "startColumn" : 4, + "endLine" : 24, + "endColumn" : 1, + "snippet" : { + "text" : "b3816fddcf28aa29d94b10ec305cd52be14c472b" + }, + "properties" : { + "secretscan.sereco.severity" : "critical" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + } ] + } ] +} \ No newline at end of file diff --git a/sechub-server/build.gradle b/sechub-server/build.gradle index 47a2e1ed1b..0c3a26b419 100644 --- a/sechub-server/build.gradle +++ b/sechub-server/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation(library.apache_commons_fileupload2_jakarta) testImplementation project(':sechub-testframework') + testImplementation project(':sechub-commons-encryption') // necessary because auf Scheduler smoke test... } task assembleArtifact(type: Zip, group: 'sechub') { diff --git a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/IntegrationTestServerPersistentTestDataCleaner.java b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/IntegrationTestServerPersistentTestDataCleaner.java index b78d88c94f..be92819e58 100644 --- a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/IntegrationTestServerPersistentTestDataCleaner.java +++ b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/IntegrationTestServerPersistentTestDataCleaner.java @@ -20,6 +20,14 @@ * persisted test data (file storage). See `PersistentScenarioTestDataProvider` * inside integration test project for more details. * + * Reason: - builds on servers can be done without gradle clean - local + * integration tests use similar names (can be useful for h2 server mode + SQL + * queries) + * + * If this is not wanted (e.g. for testing locally with a postgres database and + * restarting the server multiple times) a developer can launch the server with + * system property {@value #SKIP_AUTOCLEAN_PROPERTY} having value 'true' + * * @author Albert Tregnaghi * */ @@ -28,13 +36,40 @@ public class IntegrationTestServerPersistentTestDataCleaner { private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestServerPersistentTestDataCleaner.class); + private static final String SKIP_AUTOCLEAN_PROPERTY = "sechub.integrationtest.data.autoclean.skip"; + + @Value("${" + SKIP_AUTOCLEAN_PROPERTY + ":false}") + boolean autoCleanDisabled; + @Value("${sechub.integrationtest.ignore.missing.serverproject:false}") boolean ignoreMissingServerProject; @Bean @Order(100) @Profile(Profiles.INTEGRATIONTEST) - public CommandLineRunner dropIntegrationTestData() { + public CommandLineRunner dropOldIntegrationTestData() { + LOG.info("*".repeat(100)); + LOG.info("* Integration test auto clean"); + LOG.info("* ---------------------------"); + LOG.info("* - cleans growing ids"); + LOG.info("* - cleans all local integration test data"); + LOG.info("* - can be skipped with key: {}", SKIP_AUTOCLEAN_PROPERTY); + if (autoCleanDisabled) { + LOG.info("* - SKIPPED"); + LOG.info("*".repeat(100)); + } else { + LOG.info("* - STARTING"); + LOG.info("*".repeat(100)); + + cleanupOldIntegrationTestData(); + } + + return args -> { + }; + } + + private void cleanupOldIntegrationTestData() { + /* * When we start a new integration test server, we always drop former persisted * integration test data @@ -51,17 +86,18 @@ public CommandLineRunner dropIntegrationTestData() { } } } + File file = new File(parent, "build/sechub/integrationtest"); + String absolutePath = file.toPath().toAbsolutePath().toString(); if (file.exists()) { - LOG.info("Start removing old integration test data from {}", file.getAbsolutePath()); + LOG.info("Start removing old integration test data from {}", absolutePath); if (!FileSystemUtils.deleteRecursively(file)) { throw new IllegalStateException("Not able to destroy former integrationtest data on new integration test server startup!"); } } else { - LOG.info("No persisted integration test data found at {}", file.getAbsolutePath()); + LOG.info("No persisted integration test data found at {}", absolutePath); } - return args -> { - }; + } } diff --git a/sechub-server/src/main/resources/application-h2.properties b/sechub-server/src/main/resources/application-h2.properties index 1c83c4ceae..acc290b183 100644 --- a/sechub-server/src/main/resources/application-h2.properties +++ b/sechub-server/src/main/resources/application-h2.properties @@ -2,3 +2,5 @@ # only used in development or demo mode - so username password does not matter... spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 + +spring.flyway.locations=/db/migration/common,/db/migration/h2 \ No newline at end of file diff --git a/sechub-server/src/main/resources/application-integrationtest.yml b/sechub-server/src/main/resources/application-integrationtest.yml index 73055354e3..7e4da64df7 100644 --- a/sechub-server/src/main/resources/application-integrationtest.yml +++ b/sechub-server/src/main/resources/application-integrationtest.yml @@ -21,6 +21,14 @@ sechub: # one check (which is always done) except multiple. This is to avoid flaky tests, # but gives us possiblity to check multiple ones when necessary (default are 60 seconds # and this too long for test...) + schedule: + encryption: + refresh: + initialdelay: 100 + # every 2000 milliseconds in integration tests + delay: 2000 + accept-outdated: + milliseconds: 100 scan: scanconfig: refresh: diff --git a/sechub-server/src/main/resources/application-postgres.properties b/sechub-server/src/main/resources/application-postgres.properties index c072b3e1fe..abc8aeecd9 100644 --- a/sechub-server/src/main/resources/application-postgres.properties +++ b/sechub-server/src/main/resources/application-postgres.properties @@ -9,4 +9,6 @@ spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect # for production you must change it by setting environment entries. E.g. in k8s deployment spring.datasource.url=${POSTGRES_DB_URL} spring.datasource.username=${POSTGRES_DB_USERNAME} -spring.datasource.password=${POSTGRES_DB_PASSWORD} \ No newline at end of file +spring.datasource.password=${POSTGRES_DB_PASSWORD} + +spring.flyway.locations=/db/migration/common,/db/migration/postgres \ No newline at end of file diff --git a/sechub-server/src/main/resources/db/migration/U10__project_metadata.sql b/sechub-server/src/main/resources/db/migration/common/U10__project_metadata.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U10__project_metadata.sql rename to sechub-server/src/main/resources/db/migration/common/U10__project_metadata.sql diff --git a/sechub-server/src/main/resources/db/migration/U11__config_uuid_in_product_result.sql b/sechub-server/src/main/resources/db/migration/common/U11__config_uuid_in_product_result.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U11__config_uuid_in_product_result.sql rename to sechub-server/src/main/resources/db/migration/common/U11__config_uuid_in_product_result.sql diff --git a/sechub-server/src/main/resources/db/migration/U12__project_access_level.sql b/sechub-server/src/main/resources/db/migration/common/U12__project_access_level.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U12__project_access_level.sql rename to sechub-server/src/main/resources/db/migration/common/U12__project_access_level.sql diff --git a/sechub-server/src/main/resources/db/migration/U13__sechub_report_has_more_fields.sql b/sechub-server/src/main/resources/db/migration/common/U13__sechub_report_has_more_fields.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U13__sechub_report_has_more_fields.sql rename to sechub-server/src/main/resources/db/migration/common/U13__sechub_report_has_more_fields.sql diff --git a/sechub-server/src/main/resources/db/migration/U14__sechub_report_type_bugfix.sql b/sechub-server/src/main/resources/db/migration/common/U14__sechub_report_type_bugfix.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U14__sechub_report_type_bugfix.sql rename to sechub-server/src/main/resources/db/migration/common/U14__sechub_report_type_bugfix.sql diff --git a/sechub-server/src/main/resources/db/migration/U15__provide_auto_cleanup.sql b/sechub-server/src/main/resources/db/migration/common/U15__provide_auto_cleanup.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U15__provide_auto_cleanup.sql rename to sechub-server/src/main/resources/db/migration/common/U15__provide_auto_cleanup.sql diff --git a/sechub-server/src/main/resources/db/migration/U16__rename_admin_config_to_adm_config.sql b/sechub-server/src/main/resources/db/migration/common/U16__rename_admin_config_to_adm_config.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U16__rename_admin_config_to_adm_config.sql rename to sechub-server/src/main/resources/db/migration/common/U16__rename_admin_config_to_adm_config.sql diff --git a/sechub-server/src/main/resources/db/migration/U17__add_messages_to_scheduler_status.sql b/sechub-server/src/main/resources/db/migration/common/U17__add_messages_to_scheduler_status.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U17__add_messages_to_scheduler_status.sql rename to sechub-server/src/main/resources/db/migration/common/U17__add_messages_to_scheduler_status.sql diff --git a/sechub-server/src/main/resources/db/migration/U18__add_messages_to_product_result.sql b/sechub-server/src/main/resources/db/migration/common/U18__add_messages_to_product_result.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U18__add_messages_to_product_result.sql rename to sechub-server/src/main/resources/db/migration/common/U18__add_messages_to_product_result.sql diff --git a/sechub-server/src/main/resources/db/migration/U19__add_statistic_tables.sql b/sechub-server/src/main/resources/db/migration/common/U19__add_statistic_tables.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U19__add_statistic_tables.sql rename to sechub-server/src/main/resources/db/migration/common/U19__add_statistic_tables.sql diff --git a/sechub-server/src/main/resources/db/migration/U1__Initial_version.sql b/sechub-server/src/main/resources/db/migration/common/U1__Initial_version.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U1__Initial_version.sql rename to sechub-server/src/main/resources/db/migration/common/U1__Initial_version.sql diff --git a/sechub-server/src/main/resources/db/migration/U20__add_scan_group_to_scheduler_job2.sql b/sechub-server/src/main/resources/db/migration/common/U20__add_scan_group_to_scheduler_job2.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U20__add_scan_group_to_scheduler_job2.sql rename to sechub-server/src/main/resources/db/migration/common/U20__add_scan_group_to_scheduler_job2.sql diff --git a/sechub-server/src/main/resources/db/migration/U21__delete_old_status_keys.sql b/sechub-server/src/main/resources/db/migration/common/U21__delete_old_status_keys.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U21__delete_old_status_keys.sql rename to sechub-server/src/main/resources/db/migration/common/U21__delete_old_status_keys.sql diff --git a/sechub-server/src/main/resources/db/migration/U22__add_schedule_jobdata.sql b/sechub-server/src/main/resources/db/migration/common/U22__add_schedule_jobdata.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U22__add_schedule_jobdata.sql rename to sechub-server/src/main/resources/db/migration/common/U22__add_schedule_jobdata.sql diff --git a/sechub-server/src/main/resources/db/migration/U23__drop_spring_batch_tables.sql b/sechub-server/src/main/resources/db/migration/common/U23__drop_spring_batch_tables.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U23__drop_spring_batch_tables.sql rename to sechub-server/src/main/resources/db/migration/common/U23__drop_spring_batch_tables.sql diff --git a/sechub-server/src/main/resources/db/migration/U24__drop_and_recreate_adm_jobinformation_tables.sql b/sechub-server/src/main/resources/db/migration/common/U24__drop_and_recreate_adm_jobinformation_tables.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U24__drop_and_recreate_adm_jobinformation_tables.sql rename to sechub-server/src/main/resources/db/migration/common/U24__drop_and_recreate_adm_jobinformation_tables.sql diff --git a/sechub-server/src/main/resources/db/migration/U25__enlarge_data_fields.sql b/sechub-server/src/main/resources/db/migration/common/U25__enlarge_data_fields.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U25__enlarge_data_fields.sql rename to sechub-server/src/main/resources/db/migration/common/U25__enlarge_data_fields.sql diff --git a/sechub-server/src/main/resources/db/migration/U26__statistic_indices.sql b/sechub-server/src/main/resources/db/migration/common/U26__statistic_indices.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U26__statistic_indices.sql rename to sechub-server/src/main/resources/db/migration/common/U26__statistic_indices.sql diff --git a/sechub-server/src/main/resources/db/migration/U27__rename_emailAdress_column.sql b/sechub-server/src/main/resources/db/migration/common/U27__rename_emailAdress_column.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U27__rename_emailAdress_column.sql rename to sechub-server/src/main/resources/db/migration/common/U27__rename_emailAdress_column.sql diff --git a/sechub-server/src/main/resources/db/migration/U28__enlarge_project_id.sql b/sechub-server/src/main/resources/db/migration/common/U28__enlarge_project_id.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U28__enlarge_project_id.sql rename to sechub-server/src/main/resources/db/migration/common/U28__enlarge_project_id.sql diff --git a/sechub-server/src/main/resources/db/migration/common/U29__encryption.sql b/sechub-server/src/main/resources/db/migration/common/U29__encryption.sql new file mode 100644 index 0000000000..7be49330ef --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/common/U29__encryption.sql @@ -0,0 +1,14 @@ +-- SPDX-License-Identifier: MIT + +-- Remark: the scripts U31-U29 are only to provide the downgrade per SQL +-- but it IS NOT recommended to do this with encrypted data inside! This will +-- only work if all jobs are encrypted with NoneCipher, otherwise this means configuration data +-- loss because it will not contain valid json + +-- (U30 scripts will have copied encrypted conifg bytes as text to configuration) +ALTER TABLE schedule_sechub_job DROP COLUMN encrypted_configuration; +ALTER TABLE schedule_sechub_job DROP COLUMN encrypt_initial_vector; +ALTER TABLE schedule_sechub_job DROP COLUMN encrypt_pool_data_id; + +-- encryption parts +DROP TABLE schedule_cipher_pool_data; \ No newline at end of file diff --git a/sechub-server/src/main/resources/db/migration/common/U29__encryption_common.sql b/sechub-server/src/main/resources/db/migration/common/U29__encryption_common.sql new file mode 100644 index 0000000000..d49159b18f --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/common/U29__encryption_common.sql @@ -0,0 +1,10 @@ +-- SPDX-License-Identifier: MIT +ALTER TABLE schedule_sechub_job DROP COLUMN encrypted_configuration bytea; +ALTER TABLE schedule_sechub_job DROP COLUMN encrypt_initial_vector bytea; +ALTER TABLE schedule_sechub_job DROP COLUMN encrypt_pool_data_id integer; + +DROP TABLE IF EXISTS schedule_cipher_pool_data; + +ALTER TABLE adm_job_information ADD COLUMN configuration varchar(8912); +ALTER TABLE scan_project_log ADD COLUMN config varchar(8912); +ALTER TABLE scan_report ADD COLUMN config varchar(8912); diff --git a/sechub-server/src/main/resources/db/migration/U2__provide_scheduler_start_stop_and_status.sql b/sechub-server/src/main/resources/db/migration/common/U2__provide_scheduler_start_stop_and_status.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U2__provide_scheduler_start_stop_and_status.sql rename to sechub-server/src/main/resources/db/migration/common/U2__provide_scheduler_start_stop_and_status.sql diff --git a/sechub-server/src/main/resources/db/migration/common/U31__encryption_drop_unencrypted_config.sql b/sechub-server/src/main/resources/db/migration/common/U31__encryption_drop_unencrypted_config.sql new file mode 100644 index 0000000000..2ab7dc4c77 --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/common/U31__encryption_drop_unencrypted_config.sql @@ -0,0 +1,15 @@ +-- SPDX-License-Identifier: MIT + +-- Remark: the scripts U31-U29 are only to provide the downgrade per SQL +-- but it IS NOT recommended to do this with encrypted data inside! This will +-- only work if all jobs are encrypted with NoneCipher, otherwise this means configuration data +-- loss because it will not contain valid json + + +-- revert table structure add old columns again + +ALTER TABLE schedule_sechub_job ADD COLUMN configuration varchar(8192); +-- Only one configuration persistence shall exist inside SecHub database: +ALTER TABLE adm_job_information ADD COLUMN configuration varchar(8192); +ALTER TABLE scan_project_log DROP COLUMN config varchar(8912); +ALTER TABLE scan_report DROP COLUMN config varchar(8912); diff --git a/sechub-server/src/main/resources/db/migration/U3__store_projectid_in_product_results.sql b/sechub-server/src/main/resources/db/migration/common/U3__store_projectid_in_product_results.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U3__store_projectid_in_product_results.sql rename to sechub-server/src/main/resources/db/migration/common/U3__store_projectid_in_product_results.sql diff --git a/sechub-server/src/main/resources/db/migration/U4__provide_scan_project_config.sql b/sechub-server/src/main/resources/db/migration/common/U4__provide_scan_project_config.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U4__provide_scan_project_config.sql rename to sechub-server/src/main/resources/db/migration/common/U4__provide_scan_project_config.sql diff --git a/sechub-server/src/main/resources/db/migration/U5__provide_scanconfig_without_restarts.sql b/sechub-server/src/main/resources/db/migration/common/U5__provide_scanconfig_without_restarts.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U5__provide_scanconfig_without_restarts.sql rename to sechub-server/src/main/resources/db/migration/common/U5__provide_scanconfig_without_restarts.sql diff --git a/sechub-server/src/main/resources/db/migration/U6__provide_metadata_in_product_result.sql b/sechub-server/src/main/resources/db/migration/common/U6__provide_metadata_in_product_result.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U6__provide_metadata_in_product_result.sql rename to sechub-server/src/main/resources/db/migration/common/U6__provide_metadata_in_product_result.sql diff --git a/sechub-server/src/main/resources/db/migration/U7__provide_job_restart_changes.sql b/sechub-server/src/main/resources/db/migration/common/U7__provide_job_restart_changes.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U7__provide_job_restart_changes.sql rename to sechub-server/src/main/resources/db/migration/common/U7__provide_job_restart_changes.sql diff --git a/sechub-server/src/main/resources/db/migration/U8__enlarge_project_config_data_field.sql b/sechub-server/src/main/resources/db/migration/common/U8__enlarge_project_config_data_field.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U8__enlarge_project_config_data_field.sql rename to sechub-server/src/main/resources/db/migration/common/U8__enlarge_project_config_data_field.sql diff --git a/sechub-server/src/main/resources/db/migration/U9__runtime_product_configuration.sql b/sechub-server/src/main/resources/db/migration/common/U9__runtime_product_configuration.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/U9__runtime_product_configuration.sql rename to sechub-server/src/main/resources/db/migration/common/U9__runtime_product_configuration.sql diff --git a/sechub-server/src/main/resources/db/migration/V10__project_metadata.sql b/sechub-server/src/main/resources/db/migration/common/V10__project_metadata.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V10__project_metadata.sql rename to sechub-server/src/main/resources/db/migration/common/V10__project_metadata.sql diff --git a/sechub-server/src/main/resources/db/migration/V11__config_uuid_in_product_results.sql b/sechub-server/src/main/resources/db/migration/common/V11__config_uuid_in_product_results.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V11__config_uuid_in_product_results.sql rename to sechub-server/src/main/resources/db/migration/common/V11__config_uuid_in_product_results.sql diff --git a/sechub-server/src/main/resources/db/migration/V12__project_access_level.sql b/sechub-server/src/main/resources/db/migration/common/V12__project_access_level.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V12__project_access_level.sql rename to sechub-server/src/main/resources/db/migration/common/V12__project_access_level.sql diff --git a/sechub-server/src/main/resources/db/migration/V13__sechub_report_has_more_fields.sql b/sechub-server/src/main/resources/db/migration/common/V13__sechub_report_has_more_fields.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V13__sechub_report_has_more_fields.sql rename to sechub-server/src/main/resources/db/migration/common/V13__sechub_report_has_more_fields.sql diff --git a/sechub-server/src/main/resources/db/migration/V14__sechub_report_type_bugfix.sql b/sechub-server/src/main/resources/db/migration/common/V14__sechub_report_type_bugfix.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V14__sechub_report_type_bugfix.sql rename to sechub-server/src/main/resources/db/migration/common/V14__sechub_report_type_bugfix.sql diff --git a/sechub-server/src/main/resources/db/migration/V15__provide_auto_cleanup.sql b/sechub-server/src/main/resources/db/migration/common/V15__provide_auto_cleanup.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V15__provide_auto_cleanup.sql rename to sechub-server/src/main/resources/db/migration/common/V15__provide_auto_cleanup.sql diff --git a/sechub-server/src/main/resources/db/migration/V16__rename_admin_config_to_adm_config.sql b/sechub-server/src/main/resources/db/migration/common/V16__rename_admin_config_to_adm_config.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V16__rename_admin_config_to_adm_config.sql rename to sechub-server/src/main/resources/db/migration/common/V16__rename_admin_config_to_adm_config.sql diff --git a/sechub-server/src/main/resources/db/migration/V17__add_messages_to_scheduler_status.sql b/sechub-server/src/main/resources/db/migration/common/V17__add_messages_to_scheduler_status.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V17__add_messages_to_scheduler_status.sql rename to sechub-server/src/main/resources/db/migration/common/V17__add_messages_to_scheduler_status.sql diff --git a/sechub-server/src/main/resources/db/migration/V18__add_messages_to_product_result.sql b/sechub-server/src/main/resources/db/migration/common/V18__add_messages_to_product_result.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V18__add_messages_to_product_result.sql rename to sechub-server/src/main/resources/db/migration/common/V18__add_messages_to_product_result.sql diff --git a/sechub-server/src/main/resources/db/migration/V19__add_statistic_tables.sql b/sechub-server/src/main/resources/db/migration/common/V19__add_statistic_tables.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V19__add_statistic_tables.sql rename to sechub-server/src/main/resources/db/migration/common/V19__add_statistic_tables.sql diff --git a/sechub-server/src/main/resources/db/migration/V1__Initial_version.sql b/sechub-server/src/main/resources/db/migration/common/V1__Initial_version.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V1__Initial_version.sql rename to sechub-server/src/main/resources/db/migration/common/V1__Initial_version.sql diff --git a/sechub-server/src/main/resources/db/migration/V20__add_scan_group_to_scheduler_job.sql b/sechub-server/src/main/resources/db/migration/common/V20__add_scan_group_to_scheduler_job.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V20__add_scan_group_to_scheduler_job.sql rename to sechub-server/src/main/resources/db/migration/common/V20__add_scan_group_to_scheduler_job.sql diff --git a/sechub-server/src/main/resources/db/migration/V21__delete_old_adm_status_keys.sql b/sechub-server/src/main/resources/db/migration/common/V21__delete_old_adm_status_keys.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V21__delete_old_adm_status_keys.sql rename to sechub-server/src/main/resources/db/migration/common/V21__delete_old_adm_status_keys.sql diff --git a/sechub-server/src/main/resources/db/migration/V22__add_schedule_jobdata.sql b/sechub-server/src/main/resources/db/migration/common/V22__add_schedule_jobdata.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V22__add_schedule_jobdata.sql rename to sechub-server/src/main/resources/db/migration/common/V22__add_schedule_jobdata.sql diff --git a/sechub-server/src/main/resources/db/migration/V23__drop_spring_batch_tables.sql b/sechub-server/src/main/resources/db/migration/common/V23__drop_spring_batch_tables.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V23__drop_spring_batch_tables.sql rename to sechub-server/src/main/resources/db/migration/common/V23__drop_spring_batch_tables.sql diff --git a/sechub-server/src/main/resources/db/migration/V24__drop_and_recreate_adm_jobinformation_tables.sql b/sechub-server/src/main/resources/db/migration/common/V24__drop_and_recreate_adm_jobinformation_tables.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V24__drop_and_recreate_adm_jobinformation_tables.sql rename to sechub-server/src/main/resources/db/migration/common/V24__drop_and_recreate_adm_jobinformation_tables.sql diff --git a/sechub-server/src/main/resources/db/migration/V25__enlarge_data_fields.sql b/sechub-server/src/main/resources/db/migration/common/V25__enlarge_data_fields.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V25__enlarge_data_fields.sql rename to sechub-server/src/main/resources/db/migration/common/V25__enlarge_data_fields.sql diff --git a/sechub-server/src/main/resources/db/migration/V26__statistic_indices.sql b/sechub-server/src/main/resources/db/migration/common/V26__statistic_indices.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V26__statistic_indices.sql rename to sechub-server/src/main/resources/db/migration/common/V26__statistic_indices.sql diff --git a/sechub-server/src/main/resources/db/migration/V27__rename_emailAdress_column.sql b/sechub-server/src/main/resources/db/migration/common/V27__rename_emailAdress_column.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V27__rename_emailAdress_column.sql rename to sechub-server/src/main/resources/db/migration/common/V27__rename_emailAdress_column.sql diff --git a/sechub-server/src/main/resources/db/migration/V28__enlarge_project_id.sql b/sechub-server/src/main/resources/db/migration/common/V28__enlarge_project_id.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V28__enlarge_project_id.sql rename to sechub-server/src/main/resources/db/migration/common/V28__enlarge_project_id.sql diff --git a/sechub-server/src/main/resources/db/migration/common/V29__encryption_common.sql b/sechub-server/src/main/resources/db/migration/common/V29__encryption_common.sql new file mode 100644 index 0000000000..fead836da0 --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/common/V29__encryption_common.sql @@ -0,0 +1,34 @@ +-- SPDX-License-Identifier: MIT +ALTER TABLE schedule_sechub_job ADD COLUMN encrypted_configuration bytea; +ALTER TABLE schedule_sechub_job ADD COLUMN encrypt_initial_vector bytea; +ALTER TABLE schedule_sechub_job ADD COLUMN encrypt_pool_data_id integer; + +ALTER TABLE schedule_sechub_job ALTER COLUMN configuration DROP NOT NULL; -- now we allow null + +ALTER TABLE schedule_sechub_job RENAME COLUMN configuration to unencrypted_configuration; -- necessary to have no old server version to be able to add accidently new sechub jobs while rolling udpdates are done (e.g. K8s deployment). We accept/force here that an old server create job would crash now... + + +-- encryption parts +CREATE TABLE schedule_cipher_pool_data +( + pool_id integer not null generated by default as identity, + + pool_algorithm varchar(80) not null, + pool_pwd_src_type varchar(80) not null, + pool_pwd_src_data varchar(255), + + pool_test_text varchar(255) not null, + pool_test_initial_vector bytea not null, + pool_test_encrypted bytea not null, + + pool_creation_timestamp timestamp not null, + pool_created_from varchar(60), -- we accept 60 (3 x 20) see UserIdValidation + + version integer, + PRIMARY KEY (pool_id) +); + +-- Only one configuration persistence shall exist inside SecHub database: +ALTER TABLE adm_job_information DROP COLUMN configuration; -- we remove the configuration here +ALTER TABLE scan_project_log DROP COLUMN config; -- we remove the configuration here +ALTER TABLE scan_report DROP COLUMN config; -- we remove the configuration here diff --git a/sechub-server/src/main/resources/db/migration/V2__provide_scheduler_start_stop_and_status.sql b/sechub-server/src/main/resources/db/migration/common/V2__provide_scheduler_start_stop_and_status.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V2__provide_scheduler_start_stop_and_status.sql rename to sechub-server/src/main/resources/db/migration/common/V2__provide_scheduler_start_stop_and_status.sql diff --git a/sechub-server/src/main/resources/db/migration/common/V31__encryption_drop_unencrypted_config.sql b/sechub-server/src/main/resources/db/migration/common/V31__encryption_drop_unencrypted_config.sql new file mode 100644 index 0000000000..bdfa827d6d --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/common/V31__encryption_drop_unencrypted_config.sql @@ -0,0 +1,3 @@ +-- SPDX-License-Identifier: MIT +-- remove old unencrypted config column +ALTER TABLE schedule_sechub_job DROP COLUMN unencrypted_configuration; \ No newline at end of file diff --git a/sechub-server/src/main/resources/db/migration/V3__store_projectid_in_product_results.sql b/sechub-server/src/main/resources/db/migration/common/V3__store_projectid_in_product_results.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V3__store_projectid_in_product_results.sql rename to sechub-server/src/main/resources/db/migration/common/V3__store_projectid_in_product_results.sql diff --git a/sechub-server/src/main/resources/db/migration/V4__provide_scan_project_config.sql b/sechub-server/src/main/resources/db/migration/common/V4__provide_scan_project_config.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V4__provide_scan_project_config.sql rename to sechub-server/src/main/resources/db/migration/common/V4__provide_scan_project_config.sql diff --git a/sechub-server/src/main/resources/db/migration/V5__provide_scanconfig_without_restarts.sql b/sechub-server/src/main/resources/db/migration/common/V5__provide_scanconfig_without_restarts.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V5__provide_scanconfig_without_restarts.sql rename to sechub-server/src/main/resources/db/migration/common/V5__provide_scanconfig_without_restarts.sql diff --git a/sechub-server/src/main/resources/db/migration/V6__provide_metadata_in_product_result.sql b/sechub-server/src/main/resources/db/migration/common/V6__provide_metadata_in_product_result.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V6__provide_metadata_in_product_result.sql rename to sechub-server/src/main/resources/db/migration/common/V6__provide_metadata_in_product_result.sql diff --git a/sechub-server/src/main/resources/db/migration/V7__provide_job_restart_changes.sql b/sechub-server/src/main/resources/db/migration/common/V7__provide_job_restart_changes.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V7__provide_job_restart_changes.sql rename to sechub-server/src/main/resources/db/migration/common/V7__provide_job_restart_changes.sql diff --git a/sechub-server/src/main/resources/db/migration/V8__enlarge_project_config_data_field.sql b/sechub-server/src/main/resources/db/migration/common/V8__enlarge_project_config_data_field.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V8__enlarge_project_config_data_field.sql rename to sechub-server/src/main/resources/db/migration/common/V8__enlarge_project_config_data_field.sql diff --git a/sechub-server/src/main/resources/db/migration/V9__runtime_product_configuration.sql b/sechub-server/src/main/resources/db/migration/common/V9__runtime_product_configuration.sql similarity index 100% rename from sechub-server/src/main/resources/db/migration/V9__runtime_product_configuration.sql rename to sechub-server/src/main/resources/db/migration/common/V9__runtime_product_configuration.sql diff --git a/sechub-server/src/main/resources/db/migration/h2/U30__encryption_first_cipher_h2.sql b/sechub-server/src/main/resources/db/migration/h2/U30__encryption_first_cipher_h2.sql new file mode 100644 index 0000000000..a06266efc1 --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/h2/U30__encryption_first_cipher_h2.sql @@ -0,0 +1,8 @@ +-- SPDX-License-Identifier: MIT + +-- In postgres we do a migration +-- But here, for h2, we do not migrate old data, because +-- h2 is only for testing! +-- Means no job migration done here. + +DELETE FROM schedule_cipher_pool_data; \ No newline at end of file diff --git a/sechub-server/src/main/resources/db/migration/h2/V30__encryption_first_cipher_h2.sql b/sechub-server/src/main/resources/db/migration/h2/V30__encryption_first_cipher_h2.sql new file mode 100644 index 0000000000..a669a0722b --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/h2/V30__encryption_first_cipher_h2.sql @@ -0,0 +1,35 @@ +-- SPDX-License-Identifier: MIT +INSERT INTO schedule_cipher_pool_data ( + pool_id, + + pool_algorithm, + pool_pwd_src_type, + pool_pwd_src_data, + + pool_test_text, + pool_test_initial_vector, + pool_test_encrypted, + + pool_creation_timestamp, + pool_created_from, + version + ) +VALUES( + 0, -- pool_id + + 'NONE', -- pool_algorithm: CipherAlgorithm:NONE + 'NONE', -- pool_pwd_src_type: CipherPasswordSourceType:NONE + null, -- pool_pwd_src_data + + 'test-text1', --pool_test_text: plain text + X'6E6F6E65', -- pool_test_initial_vector: as bytes of simple text: "none" + X'746573742d7465787431', --decode('dGVzdC10ZXh0MQ==', 'base64'), -- pool_test_encrypted: "test-text1" just as plain text bytes from base64 + + now(), -- created : SQL 92 spec, + null, -- createdFrom: not user created + 0 +); + +-- In postgres we do a migration +-- But here, for h2, we do not migrate old data, because +-- h2 is only for testing! \ No newline at end of file diff --git a/sechub-server/src/main/resources/db/migration/postgres/U30__encryption_first_cipher_postgres.sql b/sechub-server/src/main/resources/db/migration/postgres/U30__encryption_first_cipher_postgres.sql new file mode 100644 index 0000000000..a1cfbc6744 --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/postgres/U30__encryption_first_cipher_postgres.sql @@ -0,0 +1,11 @@ +-- SPDX-License-Identifier: MIT + +-- Remark: the scripts U31-U29 are only to provide the downgrade per SQL +-- but it IS NOT recommended to do this with encrypted data inside! This will +-- only work if all jobs are encrypted with NoneCipher, otherwise this means configuration data +-- loss because it will not contain valid json + + +-- convert to var char - we expect unencrypted_configuration is only "encrypted" with 'NONE' +update schedule_sechub_job ssj set + configuration = convert_from(encrypted_configuration, 'UTF8') diff --git a/sechub-server/src/main/resources/db/migration/postgres/V30__encryption_first_cipher_postgres.sql b/sechub-server/src/main/resources/db/migration/postgres/V30__encryption_first_cipher_postgres.sql new file mode 100644 index 0000000000..f3f66c7fe0 --- /dev/null +++ b/sechub-server/src/main/resources/db/migration/postgres/V30__encryption_first_cipher_postgres.sql @@ -0,0 +1,41 @@ +-- SPDX-License-Identifier: MIT +INSERT INTO schedule_cipher_pool_data ( + pool_id, + + pool_algorithm, + pool_pwd_src_type, + pool_pwd_src_data, + + pool_test_text, + pool_test_initial_vector, + pool_test_encrypted, + + pool_creation_timestamp, + pool_created_from, + version + ) +VALUES( + 0, -- pool_id + + 'NONE', -- pool_algorithm: CipherAlgorithm:NONE + 'NONE', -- pool_pwd_src_type: CipherPasswordSourceType:NONE + null, -- pool_pwd_src_data + + 'test-text1', --pool_test_text: plain text + decode('bm9uZQ==', 'base64'), -- pool_test_initial_vector: as bytes of simple text: "none" + decode('dGVzdC10ZXh0MQ==', 'base64'), -- pool_test_encrypted: "test-text1" just as plain text bytes from base64 + + now(), -- created : SQL 92 spec, + null, -- createdFrom: not user created + 0 +); + +-- Migrate unencrypted data to encrypted +-- Auto convert former unencrypted data to created NoneCipher pool entry: +-- see https://www.postgresql.org/docs/current/functions-binarystring.html#FUNCTIONS-BINARYSTRING-CONVERSIONS +update schedule_sechub_job ssj set + encrypted_configuration = convert_to(unencrypted_configuration, 'UTF8'), + encrypt_initial_vector = decode('bm9uZQ==', 'base64'), -- initial_vector: as bytes of simple text: "none" + encrypt_pool_data_id = 0; + + diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/scan/resolve/TargetResolverServiceSpringBootTest.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/scan/resolve/TargetResolverServiceSpringBootTest.java deleted file mode 100644 index 807a6d6491..0000000000 --- a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/scan/resolve/TargetResolverServiceSpringBootTest.java +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan.resolve; - -import static org.junit.Assert.*; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.URI; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import com.mercedesbenz.sechub.domain.scan.NetworkTarget; -import com.mercedesbenz.sechub.domain.scan.NetworkTargetType; -import com.mercedesbenz.sechub.sharedkernel.Profiles; - -/** - * Inside application-test.properties we have defined strategies, which will - * treat "*.intranet.example.com/org" and "192.168.*.*" as INTRANET.
    - *
    - * This integration test checks if the configured values are really used - * - * @author Albert Tregnaghi - * - */ -@SpringBootTest -@TestPropertySource(locations = "classpath:application-test.yml") -@ActiveProfiles(Profiles.TEST) -public class TargetResolverServiceSpringBootTest { - - @Autowired - TargetResolverService serviceToTest; - - @Test - public void product_failure_demo_example_org__is_INTERNET() { - /* prepare */ - URI uri = URI.create("https://productfailure.demo.example.org"); - - /* execute */ - NetworkTarget found = serviceToTest.resolveTarget(uri); - - /* test */ - assertEquals(new NetworkTarget(uri, NetworkTargetType.INTERNET), found); - - } - - @Test - public void ip_172_217_22_99__IS_INTERNET() throws Exception { - /* prepare */ - InetAddress address = Inet4Address.getByName("172.217.22.99"); - - /* execute */ - NetworkTarget found = serviceToTest.resolveTarget(address); - - /* test */ - assertEquals(new NetworkTarget(address, NetworkTargetType.INTERNET), found); - - } - - @Test - public void something_intranet_example_org__is_INTRANET() { - /* prepare */ - URI uri = URI.create("https://something.intranet.example.org"); - - /* execute */ - NetworkTarget found = serviceToTest.resolveTarget(uri); - - /* test */ - assertEquals(new NetworkTarget(uri, NetworkTargetType.INTRANET), found); - - } - - @Test - public void ip_192_168_22_99__IS_INTRANET() throws Exception { - /* prepare */ - InetAddress address = Inet4Address.getByName("192.168.22.99"); - - /* execute */ - NetworkTarget found = serviceToTest.resolveTarget(address); - - /* test */ - assertEquals(new NetworkTarget(address, NetworkTargetType.INTRANET), found); - - } - - @Test - public void uri_hostname_startswith_192_IS_INTRANET() { - /* prepare */ - URI uri = URI.create("https://192.168.22.99:7777"); - - /* execute */ - NetworkTarget found = serviceToTest.resolveTarget(uri); - - /* test */ - assertEquals(new NetworkTarget(uri, NetworkTargetType.INTRANET), found); - - } - -} diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/PasswordHasherTestApplication.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/PasswordHasherTestApplication.java deleted file mode 100644 index 71ce2278f2..0000000000 --- a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/PasswordHasherTestApplication.java +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.schedule; - -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; - -public class PasswordHasherTestApplication { - - public static void main(String[] args) { - PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); - String input = args[0]; - String encoded = encoder.encode(input); - System.out.println("given:" + input); - System.out.println("encoded:" + encoded); - } -} diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerCreateJobServiceSpringBootTest.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerCreateJobServiceSpringBootTest.java deleted file mode 100644 index 3e4a046ffa..0000000000 --- a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerCreateJobServiceSpringBootTest.java +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.schedule; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Optional; -import java.util.UUID; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; -import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobFactory; -import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; -import com.mercedesbenz.sechub.sharedkernel.Profiles; -import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; -import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException; -import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; - -@SpringBootTest -@TestPropertySource(locations = "classpath:application-test.yml") -@ActiveProfiles(Profiles.TEST) -public class SchedulerCreateJobServiceSpringBootTest { - - private static final String PROJECT_ID = "project1"; - - @Autowired - private SchedulerCreateJobService serviceToTest; - - @MockBean - private SecHubJobFactory jobFactory; - - @MockBean - private SecHubJobRepository jobRepository; - - @MockBean - private UserInputAssertion assertion; - - private SecHubConfiguration configuration; - private ScheduleSecHubJob nextJob; - - private UUID jobUUID; - - private String project; - - private String projectUUID = "projectId1"; - - @BeforeEach - public void beforeEach() { - jobUUID = UUID.randomUUID(); - nextJob = mock(ScheduleSecHubJob.class); - configuration = mock(SecHubConfiguration.class); - project = "projectId"; - - when(nextJob.getProjectId()).thenReturn(project); - - when(nextJob.getUUID()).thenReturn(jobUUID); - when(nextJob.getProjectId()).thenReturn(projectUUID); - when(jobFactory.createJob(eq(configuration))).thenReturn(nextJob); - - /* prepare */ - when(jobRepository.save(nextJob)).thenReturn(nextJob); - when(jobRepository.nextJobIdToExecuteFirstInFirstOut()).thenReturn(Optional.of(jobUUID)); - } - - @Test - public void scheduling_a_new_job_to_an_unexisting_project_throws_NOT_FOUND_exception() { - /* execute + test */ - Assertions.assertThrows(NotFoundException.class, () -> { - serviceToTest.createJob("a-project-not-existing", configuration); - }); - } - - @Test - public void no_access_entry__scheduling_a_configuration__will_throw_not_found_exception() { - /* execute + test */ - Assertions.assertThrows(NotFoundException.class, () -> { - serviceToTest.createJob(PROJECT_ID, configuration); - }); - } - - @Test - public void configuration_having_no_project_gets_project_from_URL() { - /* prepare */ - when(jobRepository.save(nextJob)).thenReturn(nextJob); - - /* execute + test */ - Assertions.assertThrows(NotFoundException.class, () -> { - serviceToTest.createJob(PROJECT_ID, configuration); - }); - } - -} diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerGetJobStatusServiceTest.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerGetJobStatusServiceTest.java deleted file mode 100644 index d3d9bf2433..0000000000 --- a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerGetJobStatusServiceTest.java +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.schedule; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Optional; -import java.util.UUID; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import com.mercedesbenz.sechub.domain.schedule.access.ScheduleAccessRepository; -import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; -import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobFactory; -import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; -import com.mercedesbenz.sechub.sharedkernel.Profiles; -import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; -import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException; -import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; - -@SpringBootTest -@TestPropertySource(locations = "classpath:application-test.yml") -@ActiveProfiles(Profiles.TEST) -public class SchedulerGetJobStatusServiceTest { - - private static final String PROJECT_ID = "project1"; - - @Autowired - private SchedulerGetJobStatusService serviceToTest; - - @MockBean - private SecHubJobFactory jobFactory; - - @MockBean - private SecHubJobRepository jobRepository; - - @MockBean - private ScheduleAccessRepository projectUserAccessRepository; - - @MockBean - private UserInputAssertion assertion; - - private SecHubConfiguration configuration; - private ScheduleSecHubJob job; - - private UUID jobUUID; - - private String project; - - private String projectUUID = "projectId1"; - - @BeforeEach - public void beforeEach() { - jobUUID = UUID.randomUUID(); - job = mock(ScheduleSecHubJob.class); - configuration = mock(SecHubConfiguration.class); - project = "projectId"; - - when(job.getProjectId()).thenReturn(project); - - when(job.getUUID()).thenReturn(jobUUID); - when(job.getProjectId()).thenReturn(projectUUID); - when(jobFactory.createJob(eq(configuration))).thenReturn(job); - } - - @Test - public void get_a_job_status_from_an_unexisting_project_throws_NOT_FOUND_exception() { - /* prepare */ - UUID jobUUID = UUID.randomUUID(); - when(jobRepository.findById(jobUUID)).thenReturn(Optional.of(mock(ScheduleSecHubJob.class)));// should not be necessary, but to - - /* execute + test */ - // prevent dependency to call - // hierachy... we simulate job can be - // found - Assertions.assertThrows(NotFoundException.class, () -> { - serviceToTest.getJobStatus("a-project-not-existing", jobUUID); - }); - } - - @Test - public void get_a_job_status_from_an_exsting_project_but_no_job_throws_NOT_FOUND_exception() { - /* prepare */ - UUID jobUUID = UUID.randomUUID(); - when(jobRepository.findById(jobUUID)).thenReturn(Optional.empty()); // not found... - - /* execute + test */ - Assertions.assertThrows(NotFoundException.class, () -> { - serviceToTest.getJobStatus(PROJECT_ID, jobUUID); - }); - } - -} diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSmokeSpringBootTest.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSmokeSpringBootTest.java deleted file mode 100644 index 3ca3bbf8b2..0000000000 --- a/sechub-server/src/test/java/com/mercedesbenz/sechub/domain/schedule/SchedulerSmokeSpringBootTest.java +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.schedule; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import com.mercedesbenz.sechub.sharedkernel.Profiles; -import com.mercedesbenz.sechub.sharedkernel.RoleConstants; - -/* - * Smoke tests which checks that sechub-server spring boot container can be started and - * some defaults are as expected - * - * @author Albert Tregnaghi - * - */ -@SpringBootTest -@TestPropertySource(locations = "classpath:application-test.yml") -@WithMockUser(roles = { RoleConstants.ROLE_USER }) -@ActiveProfiles(Profiles.TEST) -public class SchedulerSmokeSpringBootTest { - - @Autowired - private SchedulerRestController schedulerRestController; - - @Autowired - SchedulerSourcecodeUploadConfiguration sourcecodeUploadConfiguration; - - @Autowired - SchedulerBinariesUploadConfiguration binariesUploadConfiguration; - - @Test - public void context_loads_and_some_defaults_are_as_expected() throws Exception { - // see https://spring.io/guides/gs/testing-web/ for details about testing with - // spring MVC test - assertThat(schedulerRestController).isNotNull(); // we test that we got the schedulerRestController. Means - the spring container - // context - // has been loaded successfully! - - /* check configuration defaults injected by container are as expected */ - assertTrue(sourcecodeUploadConfiguration.isZipValidationEnabled()); - assertTrue(sourcecodeUploadConfiguration.isChecksumValidationEnabled()); - - assertEquals(50 * 1024 * 1024, binariesUploadConfiguration.getMaxUploadSizeInBytes()); - } - -} diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/server/SecHubMultiSpringBootTest.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/server/SecHubMultiSpringBootTest.java new file mode 100644 index 0000000000..70d17f15c6 --- /dev/null +++ b/sechub-server/src/test/java/com/mercedesbenz/sechub/server/SecHubMultiSpringBootTest.java @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.server; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.URI; +import java.util.Optional; +import java.util.UUID; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import com.mercedesbenz.sechub.domain.scan.NetworkTarget; +import com.mercedesbenz.sechub.domain.scan.NetworkTargetType; +import com.mercedesbenz.sechub.domain.scan.resolve.TargetResolverService; +import com.mercedesbenz.sechub.domain.schedule.SchedulerBinariesUploadConfiguration; +import com.mercedesbenz.sechub.domain.schedule.SchedulerCreateJobService; +import com.mercedesbenz.sechub.domain.schedule.SchedulerGetJobStatusService; +import com.mercedesbenz.sechub.domain.schedule.SchedulerRestController; +import com.mercedesbenz.sechub.domain.schedule.SchedulerSourcecodeUploadConfiguration; +import com.mercedesbenz.sechub.domain.schedule.access.ScheduleAccessRepository; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionResult; +import com.mercedesbenz.sechub.domain.schedule.encryption.ScheduleEncryptionService; +import com.mercedesbenz.sechub.domain.schedule.job.ScheduleSecHubJob; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobFactory; +import com.mercedesbenz.sechub.domain.schedule.job.SecHubJobRepository; +import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; +import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException; +import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; + +/** + * Full spring boot tests which checks multiple parts. Why so much different + * tests inside one test class? Because here we launch a complete spring boot + * container with full setup (means all classes are inspected) which takes a + * long time to startup. + * + * The long startup time is for only for one spring boot test class setup, means + * adding additional tests via additional methods has nearly no time impact. + * Example: If we have 3 test classes with full spring boot setup and the full + * spring boot test setup for one class takes 7 seconds and we create 3 separate + * classes, we need 21 seconds for startup. If we combine it, we have need only + * 7 seconds. + * + * Because we want to have faster unit tests/builds we do this here this way. + * + * Combined parts inside this multi spring test are: + *
      + *
    • sechub-server spring boot container can be started
    • + *
    • some defaults are as expected
    • + *
    • target resolver works as defaults are as expected
      + *
      + * Inside application-test.properties we have defined strategies, which will + * treat "*.intranet.example.com/org" and "192.168.*.*" as INTRANET.
      + * This integration test checks if the configured values are really used
    • + *
    + * + * @author Albert Tregnaghi + * + */ +@SpringBootTest +@TestPropertySource(locations = "classpath:application-test.yml") +@WithMockUser(roles = { RoleConstants.ROLE_USER }) +@ActiveProfiles(Profiles.TEST) +public class SecHubMultiSpringBootTest { + + private static final String PROJECT_ID = "project1"; + private SecHubConfiguration configuration; + private ScheduleSecHubJob job; + + private UUID jobUUID; + + private String project; + + private String projectUUID = "projectId1"; + + @Autowired + private SchedulerRestController schedulerRestController; + + @Autowired + SchedulerSourcecodeUploadConfiguration sourcecodeUploadConfiguration; + + @Autowired + SchedulerBinariesUploadConfiguration binariesUploadConfiguration; + + @Autowired + ScheduleEncryptionService encryptionService; + + @Autowired + TargetResolverService targetResolverServiceToTest; + + @Autowired + private SchedulerGetJobStatusService getJobStatusServiceToTest; + + @Autowired + private SchedulerCreateJobService createJobServiceToTest; + + @MockBean + private SecHubJobFactory jobFactory; + + @MockBean + private SecHubJobRepository jobRepository; + + @MockBean + private ScheduleAccessRepository projectUserAccessRepository; + + @MockBean + private UserInputAssertion assertion; + + @BeforeEach + void beforeEach() { + jobUUID = UUID.randomUUID(); + job = mock(ScheduleSecHubJob.class); + configuration = mock(SecHubConfiguration.class); + project = "projectId"; + + when(job.getProjectId()).thenReturn(project); + when(jobRepository.save(job)).thenReturn(job); + + when(job.getUUID()).thenReturn(jobUUID); + when(job.getProjectId()).thenReturn(projectUUID); + when(jobFactory.createJob(eq(configuration))).thenReturn(job); + } + + @Test + void context_loads_and_some_defaults_are_as_expected() throws Exception { + // see https://spring.io/guides/gs/testing-web/ for details about testing with + // spring MVC test + assertThat(schedulerRestController).isNotNull(); // we test that we got the schedulerRestController. Means - the spring container + // context + // has been loaded successfully! + + /* check configuration defaults injected by container are as expected */ + assertTrue(sourcecodeUploadConfiguration.isZipValidationEnabled()); + assertTrue(sourcecodeUploadConfiguration.isChecksumValidationEnabled()); + + assertEquals(50 * 1024 * 1024, binariesUploadConfiguration.getMaxUploadSizeInBytes()); + + // test encryption service is initialized and works + String textToEncrypt = "i need encryption"; + ScheduleEncryptionResult encryptResult = encryptionService.encryptWithLatestCipher(textToEncrypt); + String decrypted = encryptionService.decryptToString(encryptResult.getEncryptedData(), encryptResult.getCipherPoolId(), + encryptResult.getInitialVector()); + assertEquals(textToEncrypt, decrypted); + + } + + @Test + void target_resolver_test_product_failure_demo_example_org__is_INTERNET() { + /* prepare */ + URI uri = URI.create("https://productfailure.demo.example.org"); + + /* execute */ + NetworkTarget found = targetResolverServiceToTest.resolveTarget(uri); + + /* test */ + assertEquals(new NetworkTarget(uri, NetworkTargetType.INTERNET), found); + + } + + @Test + void target_resolver_test_ip_172_217_22_99__IS_INTERNET() throws Exception { + /* prepare */ + InetAddress address = Inet4Address.getByName("172.217.22.99"); + + /* execute */ + NetworkTarget found = targetResolverServiceToTest.resolveTarget(address); + + /* test */ + assertEquals(new NetworkTarget(address, NetworkTargetType.INTERNET), found); + + } + + @Test + void target_resolver_test_something_intranet_example_org__is_INTRANET() { + /* prepare */ + URI uri = URI.create("https://something.intranet.example.org"); + + /* execute */ + NetworkTarget found = targetResolverServiceToTest.resolveTarget(uri); + + /* test */ + assertEquals(new NetworkTarget(uri, NetworkTargetType.INTRANET), found); + + } + + @Test + void target_resolver_test_ip_192_168_22_99__IS_INTRANET() throws Exception { + /* prepare */ + InetAddress address = Inet4Address.getByName("192.168.22.99"); + + /* execute */ + NetworkTarget found = targetResolverServiceToTest.resolveTarget(address); + + /* test */ + assertEquals(new NetworkTarget(address, NetworkTargetType.INTRANET), found); + + } + + @Test + void target_resolver_test_uri_hostname_startswith_192_IS_INTRANET() { + /* prepare */ + URI uri = URI.create("https://192.168.22.99:7777"); + + /* execute */ + NetworkTarget found = targetResolverServiceToTest.resolveTarget(uri); + + /* test */ + assertEquals(new NetworkTarget(uri, NetworkTargetType.INTRANET), found); + + } + + @Test + void get_a_job_status_from_an_unexisting_project_throws_NOT_FOUND_exception() { + /* prepare */ + UUID jobUUID = UUID.randomUUID(); + when(jobRepository.findById(jobUUID)).thenReturn(Optional.of(mock(ScheduleSecHubJob.class)));// should not be necessary, but to + + /* execute + test */ + // prevent dependency to call + // hierachy... we simulate job can be + // found + Assertions.assertThrows(NotFoundException.class, () -> { + getJobStatusServiceToTest.getJobStatus("a-project-not-existing", jobUUID); + }); + } + + @Test + void get_a_job_status_from_an_exsting_project_but_no_job_throws_NOT_FOUND_exception() { + /* prepare */ + UUID jobUUID = UUID.randomUUID(); + when(jobRepository.findById(jobUUID)).thenReturn(Optional.empty()); // not found... + + /* execute + test */ + Assertions.assertThrows(NotFoundException.class, () -> { + getJobStatusServiceToTest.getJobStatus(PROJECT_ID, jobUUID); + }); + } + + @Test + void create_job_scheduling_a_new_job_to_an_unexisting_project_throws_NOT_FOUND_exception() { + /* execute + test */ + Assertions.assertThrows(NotFoundException.class, () -> { + createJobServiceToTest.createJob("a-project-not-existing", configuration); + }); + } + + @Test + void create_job_no_access_entry__scheduling_a_configuration__will_throw_not_found_exception() { + /* execute + test */ + Assertions.assertThrows(NotFoundException.class, () -> { + createJobServiceToTest.createJob(PROJECT_ID, configuration); + }); + } +} diff --git a/sechub-shared-kernel/build.gradle b/sechub-shared-kernel/build.gradle index 32ac0b50f1..8d21e55f77 100644 --- a/sechub-shared-kernel/build.gradle +++ b/sechub-shared-kernel/build.gradle @@ -18,6 +18,7 @@ dependencies { implementation project(':sechub-storage-sharedvolume-spring') api project(':sechub-storage-s3-aws') implementation project(':sechub-adapter') + implementation project(':sechub-commons-encryption') implementation library.apache_commons_validator implementation library.logstashLogbackEncoder diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/DefaultEncryptionEnvironmentEntryProvider.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/DefaultEncryptionEnvironmentEntryProvider.java new file mode 100644 index 0000000000..238474c069 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/DefaultEncryptionEnvironmentEntryProvider.java @@ -0,0 +1,17 @@ +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.Profiles; + +@Component +@Profile("!" + Profiles.INTEGRATIONTEST) +public class DefaultEncryptionEnvironmentEntryProvider implements EncryptionEnvironmentEntryProvider { + + @Override + public String getBase64EncodedEnvironmentEntry(String environmentVariableName) { + return System.getenv(environmentVariableName); + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/EncryptionEnvironmentEntryProvider.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/EncryptionEnvironmentEntryProvider.java new file mode 100644 index 0000000000..3235704671 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/EncryptionEnvironmentEntryProvider.java @@ -0,0 +1,14 @@ +package com.mercedesbenz.sechub.sharedkernel.encryption; + +public interface EncryptionEnvironmentEntryProvider { + + /** + * Resolves value of an environment variable, it is assumed that the value is + * base 64 encoded + * + * @param environmentVariableName + * @return environment value + */ + String getBase64EncodedEnvironmentEntry(String environmentVariableName); + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/IntegrationTestEncryptionEnvironmentEntryProvider.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/IntegrationTestEncryptionEnvironmentEntryProvider.java new file mode 100644 index 0000000000..d9d64d805d --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/IntegrationTestEncryptionEnvironmentEntryProvider.java @@ -0,0 +1,37 @@ +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import java.nio.charset.Charset; +import java.util.Base64; +import java.util.Map; +import java.util.TreeMap; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.Profiles; + +@Component +@Profile(Profiles.INTEGRATIONTEST) +public class IntegrationTestEncryptionEnvironmentEntryProvider implements EncryptionEnvironmentEntryProvider { + + private Map encryptionEntryMap = new TreeMap<>(); + + public IntegrationTestEncryptionEnvironmentEntryProvider() { + addPasswordAsBase64ForEnvironmentVariable("INTEGRATION_TEST_SECRET_1_AES_256", "123456789012345678901234567890AX"); + } + + private void addPasswordAsBase64ForEnvironmentVariable(String environmentVariableName, String plainTextPassword) { + String base64Value = Base64.getEncoder().encodeToString(plainTextPassword.getBytes(Charset.forName("UTF-8"))); + encryptionEntryMap.put(environmentVariableName, base64Value); + } + + @Override + public String getBase64EncodedEnvironmentEntry(String environmentVariableName) { + String result = encryptionEntryMap.get(environmentVariableName); + if (result == null) { + throw new IllegalStateException("Integration test setup has no entry for variable:" + environmentVariableName); + } + return result; + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubCipherAlgorithm.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubCipherAlgorithm.java new file mode 100644 index 0000000000..2c5dd479b8 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubCipherAlgorithm.java @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import com.mercedesbenz.sechub.commons.core.MustBeKeptStable; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; + +@MustBeKeptStable("used in database as values and also inside event communication/domain messages") +public enum SecHubCipherAlgorithm { + + NONE(PersistentCipherType.NONE), + + AES_GCM_SIV_128(PersistentCipherType.AES_GCM_SIV_128), + + AES_GCM_SIV_256(PersistentCipherType.AES_GCM_SIV_256),; + + private PersistentCipherType type; + + private SecHubCipherAlgorithm(PersistentCipherType type) { + this.type = type; + } + + public PersistentCipherType getType() { + return type; + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubCipherPasswordSourceType.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubCipherPasswordSourceType.java new file mode 100644 index 0000000000..b94f3afbcb --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubCipherPasswordSourceType.java @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import com.mercedesbenz.sechub.commons.core.MustBeKeptStable; + +@MustBeKeptStable("used in database as values and also inside event communication/domain messages") +public enum SecHubCipherPasswordSourceType { + + /** + * No password + */ + NONE, + + /** + * Password comes from an environment variable, the name of the variable has to + * be defined inside password source date. + * + * Attention: The content of the variable may not be empty and MUST be + * base64 encoded! + */ + ENVIRONMENT_VARIABLE +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubDomainEncryptionData.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubDomainEncryptionData.java new file mode 100644 index 0000000000..dd2ad7e17b --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubDomainEncryptionData.java @@ -0,0 +1,77 @@ +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.TreeMap; + +//SPDX-License-Identifier: MIT +public class SecHubDomainEncryptionData { + + public static final String PROPERTY_ID = "id"; + public static final String PROPERTY_ALGORITHM = "algorithm"; + public static final String PROPERTY_PASSWORDSOURCE = "passwordSource"; + public static final String PROPERTY_CREATED = "created"; + public static final String PROPERTY_CREATED_FROM = "createdFrom"; + public static final String PROPERTY_USAGE = "usage"; + + private String id; + + private SecHubCipherAlgorithm algorithm; + + private SecHubPasswordSource passwordSource = new SecHubPasswordSource(); + + private Map usage = new TreeMap<>(); + + private String createdFrom; + + private LocalDateTime created; + + public String getId() { + return id; + } + + public void setId(String poolId) { + this.id = poolId; + } + + public SecHubCipherAlgorithm getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(SecHubCipherAlgorithm algorithm) { + this.algorithm = algorithm; + } + + public SecHubPasswordSource getPasswordSource() { + return passwordSource; + } + + public void setPasswordSource(SecHubPasswordSource passwordSource) { + this.passwordSource = passwordSource; + } + + public Map getUsage() { + return usage; + } + + public void setUsage(Map usage) { + this.usage = usage; + } + + public String getCreatedFrom() { + return createdFrom; + } + + public void setCreatedFrom(String createdFrom) { + this.createdFrom = createdFrom; + } + + public LocalDateTime getCreated() { + return created; + } + + public void setCreated(LocalDateTime created) { + this.created = created; + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubDomainEncryptionStatus.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubDomainEncryptionStatus.java new file mode 100644 index 0000000000..bf72ea737d --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubDomainEncryptionStatus.java @@ -0,0 +1,45 @@ +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import java.util.ArrayList; +import java.util.List; + +import com.mercedesbenz.sechub.commons.model.JSONable; + +public class SecHubDomainEncryptionStatus implements JSONable { + + public static final String PROPERTY_DATA = "data"; + public static final String PROPERTY_NAME = "name"; + + private static final SecHubDomainEncryptionStatus CONVERTER = new SecHubDomainEncryptionStatus(); + private String name; + + private List data = new ArrayList<>(); + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + /** + * Returns the list of encryption data entries. Each entry represents one + * encryption pool entry! + * + * @return list of encryption data entries + */ + public List getData() { + return data; + } + + public static SecHubDomainEncryptionStatus fromString(String json) { + return CONVERTER.fromJSON(json); + } + + @Override + public Class getJSONTargetClass() { + return SecHubDomainEncryptionStatus.class; + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionData.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionData.java new file mode 100644 index 0000000000..c71ef49343 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionData.java @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import com.mercedesbenz.sechub.commons.model.JSONable; + +public class SecHubEncryptionData implements JSONable { + + private static final SecHubEncryptionData CONVERTER = new SecHubEncryptionData(); + + public static final String PROPERTY_ALGORITHM = "algorithm"; + public static final String PROPERTY_PASSWORD_SOURCETYPE = "passwordSourceType"; + public static final String PROPERTY_PASSWORD_SOURCEDATA = "passwordSourceData"; + + private SecHubCipherAlgorithm algorithm; + + private SecHubCipherPasswordSourceType passwordSourceType; + + private String passwordSourceData; + + public SecHubCipherAlgorithm getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(SecHubCipherAlgorithm algorithm) { + this.algorithm = algorithm; + } + + public SecHubCipherPasswordSourceType getPasswordSourceType() { + return passwordSourceType; + } + + public void setPasswordSourceType(SecHubCipherPasswordSourceType passwordSourceType) { + this.passwordSourceType = passwordSourceType; + } + + public String getPasswordSourceData() { + return passwordSourceData; + } + + public void setPasswordSourceData(String passwordSourceData) { + this.passwordSourceData = passwordSourceData; + } + + @Override + public Class getJSONTargetClass() { + return SecHubEncryptionData.class; + } + + public static SecHubEncryptionData fromString(String dataAsString) { + return CONVERTER.fromJSON(dataAsString); + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionDataValidator.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionDataValidator.java new file mode 100644 index 0000000000..a47fc533c3 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionDataValidator.java @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + +/** + * A very simple validation - just checks that necessary fields are filled. For + * NONE cipher algorithm we need no password data. For all others the password + * source type and password source data must be defined to have no validation + * errors. + * + * @author Albert Tregnaghi + * + */ +@Component +public class SecHubEncryptionDataValidator implements Validator { + + private static final Logger LOG = LoggerFactory.getLogger(SecHubEncryptionDataValidator.class); + + @Override + public boolean supports(Class clazz) { + return SecHubEncryptionData.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + if (target instanceof SecHubEncryptionData data) { + internalValidate(data, errors); + } else { + LOG.error("Validation cannot handle object type: {}", target == null ? null : target.getClass()); + } + } + + private void internalValidate(SecHubEncryptionData data, Errors errors) { + + ValidationUtils.rejectIfEmptyOrWhitespace(errors, SecHubEncryptionData.PROPERTY_ALGORITHM, "field.required"); + + SecHubCipherAlgorithm algorithm = data.getAlgorithm(); + handleAlgorithmPasswordData(errors, algorithm); + } + + private void handleAlgorithmPasswordData(Errors errors, SecHubCipherAlgorithm algorithm) { + if (algorithm == null) { + return; + } + switch (algorithm) { + case NONE: + // no password data necessary here + break; + default: + ValidationUtils.rejectIfEmptyOrWhitespace(errors, SecHubEncryptionData.PROPERTY_PASSWORD_SOURCETYPE, "field.required"); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, SecHubEncryptionData.PROPERTY_PASSWORD_SOURCEDATA, "field.required"); + break; + + } + } +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionStatus.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionStatus.java new file mode 100644 index 0000000000..494863ff59 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionStatus.java @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import java.util.ArrayList; +import java.util.List; + +import com.mercedesbenz.sechub.commons.model.JSONable; + +public class SecHubEncryptionStatus implements JSONable { + + public static final String PROPERTY_TYPE = "type"; + public static final String PROPERTY_DOMAINS = "domains"; + + private static SecHubEncryptionStatus CONVERTER = new SecHubEncryptionStatus(); + + private String type = "encryptionStatus"; + + public String getType() { + return type; + } + + private List domains = new ArrayList<>(); + + public List getDomains() { + return domains; + } + + @Override + public Class getJSONTargetClass() { + return SecHubEncryptionStatus.class; + } + + public static SecHubEncryptionStatus fromString(String statusAsString) { + return CONVERTER.fromJSON(statusAsString); + } +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubPasswordSource.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubPasswordSource.java new file mode 100644 index 0000000000..16445af499 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubPasswordSource.java @@ -0,0 +1,25 @@ +package com.mercedesbenz.sechub.sharedkernel.encryption; + +public class SecHubPasswordSource { + public static final String PROPERTY_TYPE = "type"; + public static final String PROPERTY_DATA = "data"; + + private SecHubCipherPasswordSourceType type; + private String data; + + public void setData(String data) { + this.data = data; + } + + public void setType(SecHubCipherPasswordSourceType type) { + this.type = type; + } + + public String getData() { + return data; + } + + public SecHubCipherPasswordSourceType getType() { + return type; + } +} \ No newline at end of file diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactory.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactory.java new file mode 100644 index 0000000000..18016b2a93 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactory.java @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import java.util.Base64; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.encryption.DefaultSecretKeyProvider; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; + +@Component +public class SecHubSecretKeyProviderFactory { + + @Autowired + EncryptionEnvironmentEntryProvider encryptionEnvironmentEntryProvider; + + public SecretKeyProvider createSecretKeyProvider(PersistentCipherType cipherType, SecHubCipherPasswordSourceType passwordSourceType, + String cipherPasswordSourceData) { + if (PersistentCipherType.NONE.equals(cipherType)) { + /* none has never a secret key provider - there is just no secret */ + return null; + } + try { + return handle(cipherType, passwordSourceType, cipherPasswordSourceData); + + } catch (Exception e) { + throw new SecHubSecretKeyProviderFactoryException( + "Was not able to create key provider for cipherType: '%s', passwordSourceType: '%s', cipherPasswordSourceData: '%s'".formatted(cipherType, + passwordSourceType, cipherPasswordSourceData), + e); + } + + } + + private SecretKeyProvider handle(PersistentCipherType cipherType, SecHubCipherPasswordSourceType passwordSourceType, String cipherPasswordSourceData) { + switch (passwordSourceType) { + + case ENVIRONMENT_VARIABLE: + String environmentVariableName = cipherPasswordSourceData; + String environmentEntry = encryptionEnvironmentEntryProvider.getBase64EncodedEnvironmentEntry(environmentVariableName); + if (environmentEntry == null || environmentEntry.isBlank()) { + throw new IllegalArgumentException("The environment variable: " + environmentVariableName + " has no value!"); + } + byte[] base64decoded = Base64.getDecoder().decode(environmentEntry); + + return new DefaultSecretKeyProvider(base64decoded, cipherType); + + default: + throw new IllegalArgumentException("Password source type '%s' for cipher type: '%s' is not supported!".formatted(passwordSourceType, cipherType)); + } + } +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactoryException.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactoryException.java new file mode 100644 index 0000000000..1a9f8749c5 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactoryException.java @@ -0,0 +1,11 @@ +package com.mercedesbenz.sechub.sharedkernel.encryption; + +public class SecHubSecretKeyProviderFactoryException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public SecHubSecretKeyProviderFactoryException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/jpa/TypedQuerySupport.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/jpa/TypedQuerySupport.java index 4a57f81c39..5f82c8b745 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/jpa/TypedQuerySupport.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/jpa/TypedQuerySupport.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.sharedkernel.jpa; +import java.util.List; import java.util.Optional; import jakarta.persistence.NoResultException; @@ -48,4 +49,9 @@ public T getSingleResultOrNull(Query query) { } throw new IllegalStateException("The given query returns not expected type:" + clazz + " but " + result.getClass()); } + + @SuppressWarnings("unchecked") + public List getList(Query query) { + return query.getResultList(); + } } diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessageService.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessageService.java index 5f156001fa..5d749b6a51 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessageService.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/DomainMessageService.java @@ -45,7 +45,7 @@ public DomainMessageService(List injectedSynchronousHand Set messageIds = getSupportedMessageIdsFor(handler); for (MessageID messageId : messageIds) { synchronHandlers.put(messageId, handler); - LOG.debug("Registered synchron message handler:{} for message ID:{}", handler.getClass(), messageId); + LOG.trace("Registered synchron message handler:{} for message ID:{}", handler.getClass(), messageId); } } @@ -61,7 +61,7 @@ public DomainMessageService(List injectedSynchronousHand this.asynchronHandlers.put(messageId, foundAsynchronousHandlersForID); } foundAsynchronousHandlersForID.add(handler); - LOG.debug("Registered asynchronus message handler:{} for message ID:{}", handler.getClass(), messageId); + LOG.trace("Registered asynchronus message handler:{} for message ID:{}", handler.getClass(), messageId); } } } diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/JobMessage.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/JobMessage.java index eaafdf361b..092b7037a1 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/JobMessage.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/JobMessage.java @@ -32,8 +32,6 @@ public class JobMessage implements JSONable { private String info; - private String configuration; - @JsonFormat(pattern = ("yyyy/MM/dd HH:mm:ss")) @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @@ -59,14 +57,6 @@ public void setInfo(String info) { this.info = info; } - public String getConfiguration() { - return configuration; - } - - public void setConfiguration(String configuration) { - this.configuration = configuration; - } - public void setSince(LocalDateTime date) { this.since = date; } diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java index 9854d242ae..a4203dca50 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageDataKeys.java @@ -6,6 +6,8 @@ import com.mercedesbenz.sechub.commons.model.SecHubMessagesList; import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionStatus; +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; /** * @@ -32,6 +34,8 @@ public class MessageDataKeys { private static final AdministrationConfigMessageDataProvider ADMIN_CONFIG_MESSAGE_DATA_PROVIDER = new AdministrationConfigMessageDataProvider(); private static final UserMessageDataProvider USER_MESSAGE_DATA_PROVIDER = new UserMessageDataProvider(); private static final LocalDateTimeMessageDataProvider LOCAL_DATE_TIME_MESSAGE_DATA_PROVIDER = new LocalDateTimeMessageDataProvider(); + private static final SecHubEncryptionMessageDataProvider SECHUB_ENCRYPTION_MESSAGE_DATA_PROVIDER = new SecHubEncryptionMessageDataProvider(); + private static final SecHubEncryptionStatusMessageDataProvider SECHUB_DOMAIN_ENCRYPTION_STATUS_MESSAGE_DATA_PROVIDER = new SecHubEncryptionStatusMessageDataProvider(); /* * Only reason why this is not an emum is that we want to have generic type @@ -51,13 +55,21 @@ private MessageDataKeys() { public static final MessageDataKey SECHUB_JOB_UUID = createKey("sechub.job.uuid", UID_MESSAGE_DATA_PROVIDER); public static final MessageDataKey SECHUB_EXECUTION_UUID = createKey("sechub.execution.uuid", UID_MESSAGE_DATA_PROVIDER); + + public static final MessageDataKey SECHUB_ENCRYPT_ROTATION_DATA = createKey("sechub.encrypt.rotation.data", + SECHUB_ENCRYPTION_MESSAGE_DATA_PROVIDER); + + public static final MessageDataKey SECHUB_DOMAIN_ENCRYPTION_STATUS = createKey("sechub.domain.encryption.status", + SECHUB_DOMAIN_ENCRYPTION_STATUS_MESSAGE_DATA_PROVIDER); + /** * Use this generic key when you just want to define timestamp without using a * dedicated model where it is already contained. */ public static final MessageDataKey LOCAL_DATE_TIME_SINCE = createKey("localdatetime.since", LOCAL_DATE_TIME_MESSAGE_DATA_PROVIDER); - public static final MessageDataKey SECHUB_CONFIG = createKey("sechub.config", SECHUB_CONFIGURATION_MESSAGE_DATA_PROVIDER); + public static final MessageDataKey SECHUB_UNENCRYPTED_CONFIG = createKey("sechub.unencryptedconfig", + SECHUB_CONFIGURATION_MESSAGE_DATA_PROVIDER); public static final MessageDataKey ENVIRONMENT_CLUSTER_MEMBER_STATUS = createKey("environment.cluster.member.status", CLUSTER_MEMBER_MESSAGE_DATA_PROVIDER); diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java index f0f961d451..63134169f6 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/MessageID.java @@ -8,21 +8,21 @@ public enum MessageID { /** - * Is send/received synchron + * Is send/received synchronous */ START_SCAN( /* @formatter:off */ MessageDataKeys.SECHUB_JOB_UUID, MessageDataKeys.EXECUTED_BY, - MessageDataKeys.SECHUB_CONFIG), + MessageDataKeys.SECHUB_UNENCRYPTED_CONFIG), /* @formatter:on */ /** - * Is send/received synchron + * Is send/received synchronous */ SCAN_DONE, /** - * Is send/received synchron + * Is send/received synchronous */ SCAN_FAILED, @@ -192,6 +192,28 @@ public enum MessageID { BINARY_UPLOAD_DONE(MessageDataKeys.SECHUB_JOB_UUID, MessageDataKeys.UPLOAD_STORAGE_DATA), + /** + * This message will be send when an administrator defines new encryption data + */ + START_ENCRYPTION_ROTATION(MessageDataKeys.SECHUB_ENCRYPT_ROTATION_DATA, MessageDataKeys.EXECUTED_BY), + + /** + * Event is sent when a scheduler instance creates or recreates its encryption + * pool + */ + SCHEDULE_ENCRYPTION_POOL_INITIALIZED, + + /** + * Is send/received synchronous + */ + GET_ENCRYPTION_STATUS_SCHEDULE_DOMAIN, + + /** + * Contains result for encryption status request by + * {@link #GET_ENCRYPTION_STATUS_SCHEDULE_DOMAIN} + */ + RESULT_ENCRYPTION_STATUS_SCHEDULE_DOMAIN(MessageDataKeys.SECHUB_DOMAIN_ENCRYPTION_STATUS), + ; private Set> unmodifiableKeys; diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/SecHubEncryptionMessageDataProvider.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/SecHubEncryptionMessageDataProvider.java new file mode 100644 index 0000000000..5b5609e22a --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/SecHubEncryptionMessageDataProvider.java @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.messaging; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubEncryptionData; + +public class SecHubEncryptionMessageDataProvider implements MessageDataProvider { + + private static final Logger LOG = LoggerFactory.getLogger(SecHubEncryptionMessageDataProvider.class); + + @Override + public SecHubEncryptionData get(String encryptionDataAsJson) { + if (encryptionDataAsJson == null) { + return null; + } + try { + return SecHubEncryptionData.fromString(encryptionDataAsJson); + } catch (IllegalArgumentException e) { + LOG.error("Cannot transform json to sechub encryption data", e); + return null; + } + } + + @Override + public String getString(SecHubEncryptionData data) { + if (data == null) { + return null; + } + return data.toJSON(); + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/SecHubEncryptionStatusMessageDataProvider.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/SecHubEncryptionStatusMessageDataProvider.java new file mode 100644 index 0000000000..74011b74f8 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/messaging/SecHubEncryptionStatusMessageDataProvider.java @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.messaging; + +import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubDomainEncryptionStatus; + +public class SecHubEncryptionStatusMessageDataProvider implements MessageDataProvider { + + @Override + public SecHubDomainEncryptionStatus get(String json) { + if (json == null) { + return null; + } + return SecHubDomainEncryptionStatus.fromString(json); + } + + @Override + public String getString(SecHubDomainEncryptionStatus data) { + if (data == null) { + return null; + } + return data.toJSON(); + } + +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseGroup.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseGroup.java index b3c6e66241..b7fed0dc4d 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseGroup.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseGroup.java @@ -23,6 +23,8 @@ public enum UseCaseGroup { CONFIGURATION("Configuration", "Usecases for configuration parts"), + ENCRYPTION("Encryption", "Usecases for encryption parts"), + OTHER("Other", "All other use cases"), ; diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java index cb6218810f..aad7de68aa 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java @@ -163,6 +163,17 @@ public enum UseCaseIdentifier { UC_ADMIN_SHOWS_USER_DETAILS_FOR_EMAIL_ADDRESS(72), + /* encryption */ + UC_ADMIN_STARTS_ENCRYPTION_ROTATION(73), + + UC_SCHEDULE_ENCRYPTION_POOL_REFRESH(74, false), + + UC_SCHEDULE_ROTATE_DATA_ENCRYPTION(75, false), + + UC_ADMIN_FETCHES_ENCRYPTION_STATUS(76), + + UC_ENCRYPTION_CLEANUP(77, false), // encryption cleanup is done by auto cleanup mechanism, no REST call necessary + ; /* +-----------------------------------------------------------------------+ */ diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseAdminFetchesEncryptionStatus.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseAdminFetchesEncryptionStatus.java new file mode 100644 index 0000000000..a5492edf2c --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseAdminFetchesEncryptionStatus.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.usecases.encryption; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseDefinition; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseGroup; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; + +/* @formatter:off */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@UseCaseDefinition( + id=UseCaseIdentifier.UC_ADMIN_FETCHES_ENCRYPTION_STATUS, + group=UseCaseGroup.ENCRYPTION, + apiName="adminFetchesEncryptionStatus", + title="Admin fetches encryption status", + description="An administrator fetches encryption status from all domains where encryption is used.") +public @interface UseCaseAdminFetchesEncryptionStatus{ + + Step value(); +} +/* @formatter:on */ diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseAdminStartsEncryptionRotation.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseAdminStartsEncryptionRotation.java new file mode 100644 index 0000000000..2edf05ebb1 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseAdminStartsEncryptionRotation.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.usecases.encryption; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseDefinition; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseGroup; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; + +/* @formatter:off */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@UseCaseDefinition( + id=UseCaseIdentifier.UC_ADMIN_STARTS_ENCRYPTION_ROTATION, + group=UseCaseGroup.ENCRYPTION, + apiName="adminStartsEncryptionRotation", + title="Admin starts encryption rotation", + description="An administrator starts encryption rotation.") +public @interface UseCaseAdminStartsEncryptionRotation{ + + Step value(); +} +/* @formatter:on */ diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseEncryptionCleanup.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseEncryptionCleanup.java new file mode 100644 index 0000000000..654ad9f012 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseEncryptionCleanup.java @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.usecases.encryption; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseDefinition; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseGroup; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; + +/* @formatter:off */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@UseCaseDefinition( + id=UseCaseIdentifier.UC_ENCRYPTION_CLEANUP, + group=UseCaseGroup.ENCRYPTION, + apiName="systemStartsEncryptionCleanup", + title="SecHub does cleanup encryption", + description=""" + Secub does an ecnryption cleanup. + + Inside relevant domains the encryption situation will be checked and + old encryption setup, which is no longer necessary, will be dropped. + + For example: When encryption was done with formerly via ENV variable + `SECRET_1_AES_256` and the new one setup is using `SECRET_2_AES_256` and + all jobs have been migrated to the new encryption, the cipher setup + using `SECRET_1_AES_256` will become obsolete and will be automatically + removed. After the remove is done, there is no longer a need to + start the server with `SECRET_1_AES_256`, but only with `SECRET_2_AES_256` ... + + """) +public @interface UseCaseEncryptionCleanup{ + + Step value(); +} +/* @formatter:on */ diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseScheduleEncryptionPoolRefresh.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseScheduleEncryptionPoolRefresh.java new file mode 100644 index 0000000000..e0b87e0724 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseScheduleEncryptionPoolRefresh.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.usecases.encryption; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseDefinition; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseGroup; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; + +/* @formatter:off */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@UseCaseDefinition( + id=UseCaseIdentifier.UC_SCHEDULE_ENCRYPTION_POOL_REFRESH, + group=UseCaseGroup.ENCRYPTION, + apiName="serverStartsEncryptionPoolRefresh", + title="Scheduler encryption pool refresh", + description="The scheduler refreshes its encryption pool data to handle new setup") +public @interface UseCaseScheduleEncryptionPoolRefresh{ + + Step value(); +} +/* @formatter:on */ diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseScheduleRotateDataEncryption.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseScheduleRotateDataEncryption.java new file mode 100644 index 0000000000..59dc8c319d --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/encryption/UseCaseScheduleRotateDataEncryption.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.usecases.encryption; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseDefinition; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseGroup; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; + +/* @formatter:off */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@UseCaseDefinition( + id=UseCaseIdentifier.UC_SCHEDULE_ROTATE_DATA_ENCRYPTION, + group=UseCaseGroup.ENCRYPTION, + apiName="serverRotatesScheduleDataEncryption", + title="Scheduler rotates data encryption", + description="The scheduler checks for old encrypted data and will encrypt with latest cipher") +public @interface UseCaseScheduleRotateDataEncryption{ + + Step value(); +} +/* @formatter:on */ diff --git a/sechub-shared-kernel/src/test/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionStatusTest.java b/sechub-shared-kernel/src/test/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionStatusTest.java new file mode 100644 index 0000000000..c98cf9d3d1 --- /dev/null +++ b/sechub-shared-kernel/src/test/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubEncryptionStatusTest.java @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SecHubEncryptionStatusTest { + + @Test + void json_transformation_works_as_expected() { + /* prepare */ + SecHubEncryptionStatus status = new SecHubEncryptionStatus(); + + SecHubDomainEncryptionData scheduleEntry0 = new SecHubDomainEncryptionData(); + scheduleEntry0.setAlgorithm(SecHubCipherAlgorithm.NONE); + scheduleEntry0.setId("0"); + scheduleEntry0.getPasswordSource().setData(null); + scheduleEntry0.getPasswordSource().setType(SecHubCipherPasswordSourceType.NONE); + + scheduleEntry0.getUsage().put("job.state.canceled", 0L); + scheduleEntry0.getUsage().put("job.state.ended", 0L); + scheduleEntry0.getUsage().put("job.state.initialized", 2L); + + SecHubDomainEncryptionData scheduleEntry1 = new SecHubDomainEncryptionData(); + scheduleEntry1.setAlgorithm(SecHubCipherAlgorithm.AES_GCM_SIV_128); + scheduleEntry1.setId("1"); + scheduleEntry1.getPasswordSource().setData("SECRET_1"); + scheduleEntry1.getPasswordSource().setType(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + + scheduleEntry1.getUsage().put("job.state.canceled", 100L); + scheduleEntry1.getUsage().put("job.state.ended", 200L); + scheduleEntry1.getUsage().put("job.state.initialized", 1L); + + SecHubDomainEncryptionStatus schedulerStatus = new SecHubDomainEncryptionStatus(); + schedulerStatus.setName("schedule"); + schedulerStatus.getData().add(scheduleEntry0); + schedulerStatus.getData().add(scheduleEntry1); + + status.getDomains().add(schedulerStatus); + + /* execute */ + String json = status.toJSON(); + + /* test */ + assertNotNull(json); + + /* execute 2 */ + SecHubEncryptionStatus status2 = SecHubEncryptionStatus.fromString(json); + + /* test 2 */ + + List domains2 = status2.getDomains(); + assertThat(domains2).hasSize(1); + + SecHubDomainEncryptionStatus schedulerStatus2 = domains2.iterator().next(); + assertThat(schedulerStatus2.getName()).isEqualTo("schedule"); + + List data2 = schedulerStatus2.getData(); + assertThat(data2).hasSize(2); + + Iterator iterator = data2.iterator(); + SecHubDomainEncryptionData schedule2Entry0 = iterator.next(); + SecHubDomainEncryptionData schedule2Entry1 = iterator.next(); + /* @formatter:off */ + assertThat(schedule2Entry0.getAlgorithm()).isEqualTo(SecHubCipherAlgorithm.NONE); + assertThat(schedule2Entry0.getId()).isEqualTo("0"); + assertThat(schedule2Entry0.getPasswordSource().getType()).isEqualTo(SecHubCipherPasswordSourceType.NONE); + assertThat(schedule2Entry0.getPasswordSource().getData()).isNull(); + assertThat(schedule2Entry0.getUsage()).containsAllEntriesOf( + Map.of( + "job.state.canceled", 0L, + "job.state.ended", 0L, + "job.state.initialized", 2L + )); + + assertThat(schedule2Entry1.getAlgorithm()).isEqualTo(SecHubCipherAlgorithm.AES_GCM_SIV_128); + assertThat(schedule2Entry1.getId()).isEqualTo("1"); + assertThat(schedule2Entry1.getPasswordSource().getType()).isEqualTo(SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE); + assertThat(schedule2Entry1.getPasswordSource().getData()).isEqualTo("SECRET_1"); + assertThat(schedule2Entry1.getUsage()).containsAllEntriesOf( + Map.of( + "job.state.canceled", 100L, + "job.state.ended", 200L, + "job.state.initialized", 1L + )); + /* @formatter:on */ + + } + + @BeforeEach + public void beforeEach() throws Exception { + + } + +} diff --git a/sechub-shared-kernel/src/test/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactoryTest.java b/sechub-shared-kernel/src/test/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactoryTest.java new file mode 100644 index 0000000000..ceb51fca73 --- /dev/null +++ b/sechub-shared-kernel/src/test/java/com/mercedesbenz/sechub/sharedkernel/encryption/SecHubSecretKeyProviderFactoryTest.java @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.encryption; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Base64; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; +import com.mercedesbenz.sechub.commons.encryption.SecretKeyProvider; + +class SecHubSecretKeyProviderFactoryTest { + + private static final Logger LOG = LoggerFactory.getLogger(SecHubSecretKeyProviderFactoryTest.class); + + private SecHubSecretKeyProviderFactory factoryToTest; + private EncryptionEnvironmentEntryProvider encryptionEnvironmentEntryProvider; + + @BeforeEach + void beforeEach() { + factoryToTest = new SecHubSecretKeyProviderFactory(); + encryptionEnvironmentEntryProvider = mock(EncryptionEnvironmentEntryProvider.class); + + factoryToTest.encryptionEnvironmentEntryProvider = encryptionEnvironmentEntryProvider; + + } + + @ParameterizedTest + @EnumSource(SecHubCipherPasswordSourceType.class) + void none_cipher_no_encryptionEnvironmentEntryProvider_used_no_matter_which_pwd_source_type_defined(SecHubCipherPasswordSourceType type) { + /* execute */ + SecretKeyProvider result = factoryToTest.createSecretKeyProvider(PersistentCipherType.NONE, type, "SECRET_1"); + + /* test */ + assertThat(result).isNull(); + + } + + @ParameterizedTest + @NullSource + @EmptySource + @ValueSource(strings = { "ZW52aXJvbm1lbnQtZW50cnktcGxhaW4tdGV4dA==" }) // valid base 64 + void cipher_aes_256_with_none_source_type_is_not_supported(String sourceData) { + /* @formatter:off */ + assertThatThrownBy(()->factoryToTest.createSecretKeyProvider(PersistentCipherType.AES_GCM_SIV_256, SecHubCipherPasswordSourceType.NONE, sourceData)). + isInstanceOf(SecHubSecretKeyProviderFactoryException.class). + hasMessage("Was not able to create key provider for cipherType: 'AES_GCM_SIV_256', passwordSourceType: 'NONE', cipherPasswordSourceData: '%s'", sourceData). + hasRootCauseExactlyInstanceOf(IllegalArgumentException.class). + hasRootCauseMessage("Password source type 'NONE' for cipher type: 'AES_GCM_SIV_256' is not supported!"); + /* @formatter:on */ + } + + @ParameterizedTest + @NullSource + @EmptySource + @ValueSource(strings = { "ZW52aXJvbm1lbnQtZW50cnktcGxhaW4tdGV4dA==" }) // valid base 64 + void cipher_aes_128_with_none_source_type_is_not_supported(String sourceData) { + /* @formatter:off */ + assertThatThrownBy(()->factoryToTest.createSecretKeyProvider(PersistentCipherType.AES_GCM_SIV_128, SecHubCipherPasswordSourceType.NONE, sourceData)). + isInstanceOf(SecHubSecretKeyProviderFactoryException.class). + hasMessage("Was not able to create key provider for cipherType: 'AES_GCM_SIV_128', passwordSourceType: 'NONE', cipherPasswordSourceData: '%s'", sourceData). + hasRootCauseExactlyInstanceOf(IllegalArgumentException.class). + hasRootCauseMessage("Password source type 'NONE' for cipher type: 'AES_GCM_SIV_128' is not supported!"); + /* @formatter:on */ + } + + @ParameterizedTest + @NullSource + @EmptySource + void when_environment_variable_not_defined_exception_is_thrown(String value) throws Exception { + + /* prepare */ + when(encryptionEnvironmentEntryProvider.getBase64EncodedEnvironmentEntry("SECRET_1")).thenReturn(value); + + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> factoryToTest.createSecretKeyProvider(PersistentCipherType.AES_GCM_SIV_256, + SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE, "SECRET_1")). + isInstanceOf(SecHubSecretKeyProviderFactoryException.class). + hasMessage("Was not able to create key provider for cipherType: 'AES_GCM_SIV_256', passwordSourceType: 'ENVIRONMENT_VARIABLE', cipherPasswordSourceData: 'SECRET_1'"). + hasRootCauseExactlyInstanceOf(IllegalArgumentException.class). + hasRootCauseMessage("The environment variable: SECRET_1 has no value!"); + /* @formatter:on */ + + } + + @ParameterizedTest + @ValueSource(strings = { "a$lbert", "1:2", " ZW52aXJvbm1lbnQtZW50cnktcGxhaW4tdGV4dA==" }) + void when_environment_variable_has_not_base_64_encoded_value_exception_is_thrown(String value) throws Exception { + + /* prepare */ + when(encryptionEnvironmentEntryProvider.getBase64EncodedEnvironmentEntry("SECRET_2")).thenReturn(value); + + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> factoryToTest.createSecretKeyProvider(PersistentCipherType.AES_GCM_SIV_128, SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE, "SECRET_2")). + isInstanceOf(SecHubSecretKeyProviderFactoryException.class). + hasMessage("Was not able to create key provider for cipherType: 'AES_GCM_SIV_128', passwordSourceType: 'ENVIRONMENT_VARIABLE', cipherPasswordSourceData: 'SECRET_2'"). + hasRootCauseExactlyInstanceOf(IllegalArgumentException.class); + /* @formatter:on */ + + } + + @ParameterizedTest + @ValueSource(strings = { "ZW52aXJvbm1lbnQtZW50cnktcGxhaW4tdGV4dA== ", "ZW52aXJvbm1lbnQtZW50cnktcGxhaW4tdGV4dA== ", + "ZW52aXJvbm1lbnQtZW50cnktcGxhaW4tdGV4dA==\t" }) + void when_environment_variable_base64_encoded_value_but_value_ends_with_whitspace_exception_is_thrown(String value) throws Exception { + + /* prepare */ + when(encryptionEnvironmentEntryProvider.getBase64EncodedEnvironmentEntry("SECRET_3")).thenReturn(value); + + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> factoryToTest.createSecretKeyProvider(PersistentCipherType.AES_GCM_SIV_256, SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE, "SECRET_3")). + isInstanceOf(SecHubSecretKeyProviderFactoryException.class). + hasMessage("Was not able to create key provider for cipherType: 'AES_GCM_SIV_256', passwordSourceType: 'ENVIRONMENT_VARIABLE', cipherPasswordSourceData: 'SECRET_3'"). + hasRootCauseExactlyInstanceOf(IllegalArgumentException.class). + hasRootCauseMessage("Input byte array has incorrect ending byte at 40"); + /* @formatter:on */ + + } + + @Test + void when_environment_variable_is_base_64_provider_has_secret_key_with_base_64_decrypted_data_inside() throws Exception { + + /* prepare */ + String plainTextAsString = "environment-entry-plain-text2"; + byte[] plainTextAsBytes = plainTextAsString.getBytes(); + String base64String = Base64.getEncoder().encodeToString(plainTextAsBytes); + LOG.info("base64String:" + base64String); + when(encryptionEnvironmentEntryProvider.getBase64EncodedEnvironmentEntry("SECRET_1")).thenReturn(base64String); + + /* execute */ + SecretKeyProvider result = factoryToTest.createSecretKeyProvider(PersistentCipherType.AES_GCM_SIV_256, + SecHubCipherPasswordSourceType.ENVIRONMENT_VARIABLE, "SECRET_1"); + + /* test */ + assertThat(result).isNotNull(); + byte[] encoded = result.getSecretKey().getEncoded(); + assertThat(encoded).isEqualTo(plainTextAsBytes); + + } + +} diff --git a/sechub-solution/docker/SecHub-Alpine.dockerfile b/sechub-solution/docker/SecHub-Alpine.dockerfile index ed4f19d0b1..e4237839fe 100644 --- a/sechub-solution/docker/SecHub-Alpine.dockerfile +++ b/sechub-solution/docker/SecHub-Alpine.dockerfile @@ -112,7 +112,7 @@ COPY copy/sechub-server-*.jar "$SECHUB_ARTIFACT_FOLDER" # Builder #------------------- -FROM builder-${BUILD_TYPE} as builder +FROM builder-${BUILD_TYPE} AS builder #------------------- # SecHub Server Image @@ -149,7 +149,8 @@ COPY --from=builder "$SECHUB_ARTIFACT_FOLDER" "$SECHUB_FOLDER" COPY --chmod=755 install-java/alpine "$SECHUB_FOLDER/install-java/" # Update packages -RUN apk update +RUN apk update && \ + apk add --no-cache curl netcat-openbsd # Install Java RUN cd "$SECHUB_FOLDER/install-java/" && \ diff --git a/sechub-solution/docker/SecHub-Debian.dockerfile b/sechub-solution/docker/SecHub-Debian.dockerfile index 7b01e501b4..0de68d2d68 100644 --- a/sechub-solution/docker/SecHub-Debian.dockerfile +++ b/sechub-solution/docker/SecHub-Debian.dockerfile @@ -141,7 +141,7 @@ COPY copy/sechub-server-*.jar "$SECHUB_ARTIFACT_FOLDER" # Builder #------------------- -FROM builder-${BUILD_TYPE} as builder +FROM builder-${BUILD_TYPE} AS builder #------------------- # SecHub Server Image @@ -182,6 +182,7 @@ RUN chmod +x /run.sh RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ apt-get upgrade --assume-yes --quiet && \ + apt-get install --assume-yes curl netcat-openbsd && \ apt-get clean COPY --chmod=755 install-java/debian/ "$SECHUB_FOLDER/install-java/" diff --git a/sechub-solution/docker/run.sh b/sechub-solution/docker/run.sh index a90f848f08..2200964d29 100755 --- a/sechub-solution/docker/run.sh +++ b/sechub-solution/docker/run.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env sh +#!/bin/sh # SPDX-License-Identifier: MIT SLEEP_TIME_IN_WAIT_LOOP="2h" @@ -41,15 +41,18 @@ localserver() { check_setup profiles="dev,real_products,mocked_notifications" - storage_options="-Dsechub.storage.sharedvolume.upload.dir=$SECHUB_STORAGE_SHAREDVOLUME_UPLOAD_DIR" - if [ "$POSTGRES_ENABLED" = true ] - then + if [ "$POSTGRES_ENABLED" = true ] ; then profiles="$profiles,postgres" check_variable "$DATABASE_CONNECTION" "DATABASE_CONNECTION" check_variable "$DATABASE_USERNAME" "DATABASE_USERNAME" check_variable "$DATABASE_PASSWORD" "DATABASE_PASSWORD" - database_options="-Dspring.datasource.url=$DATABASE_CONNECTION -Dspring.datasource.username=$DATABASE_USERNAME -Dspring.datasource.password=$DATABASE_PASSWORD" + + # Set datasource variables for Java Spring app: + export SPRING_DATASOURCE_URL="$DATABASE_CONNECTION" + export SPRING_DATASOURCE_USERNAME="$DATABASE_USERNAME" + export SPRING_DATASOURCE_PASSWORD="$DATABASE_PASSWORD" + echo "Using database: Postgres" echo " * connection string: $DATABASE_CONNECTION" echo " * database username: $DATABASE_USERNAME" @@ -62,27 +65,31 @@ localserver() { echo "Upload binaries maximum bytes: $SECHUB_UPLOAD_BINARIES_MAXIMUM_BYTES" echo "Activated Spring profiles: $profiles" + # Check variables for Java Spring app: + check_variable "$SECHUB_UPLOAD_BINARIES_MAXIMUM_BYTES" "SECHUB_UPLOAD_BINARIES_MAXIMUM_BYTES" + + # Set variables for Java Spring app: + export SPRING_PROFILES_ACTIVE="$profiles" + export SECHUB_TARGETTYPE_DETECTION_INTRANET_HOSTNAME_ENDSWITH="intranet.example.org" + export SECHUB_CONFIG_TRIGGER_NEXTJOB_INITIALDELAY="0" + export SECHUB_INITIALADMIN_USERID="$ADMIN_USERID" + export SECHUB_INITIALADMIN_APITOKEN="$ADMIN_APITOKEN" + export SECHUB_INITIALADMIN_EMAIL="sechubadm@example.org" + export SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE="$SECHUB_MAX_FILE_UPLOAD_SIZE" + export SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE="$SECHUB_MAX_FILE_UPLOAD_SIZE" + export SECHUB_ADAPTER_NETSPARKER_USERID="abc" + export SECHUB_ADAPTER_NETSPARKER_APITOKEN="xyz" + export SECHUB_ADAPTER_NETSPARKER_BASEURL="https://example.org" + export SECHUB_ADAPTER_NETSPARKER_DEFAULTPOLICYID="example" + export SECHUB_ADAPTER_NETSPARKER_LICENSEID="example" + export SECHUB_ADAPTER_NESSUS_DEFAULTPOLICYID="example" + export SECHUB_NOTIFICATION_EMAIL_ADMINISTRATORS="example@example.org" + export SECHUB_NOTIFICATION_EMAIL_FROM="example@example.org" + export SECHUB_NOTIFICATION_SMTP_HOSTNAME="example.org" + echo "Starting up SecHub server" - java $java_debug_options $database_options $storage_options \ + java $java_debug_options \ -Dfile.encoding=UTF-8 \ - -Dspring.profiles.active="$profiles" \ - -Dsechub.targettype.detection.intranet.hostname.endswith=intranet.example.org \ - -Dsechub.config.trigger.nextjob.initialdelay=0 \ - -Dsechub.initialadmin.userid="$ADMIN_USERID" \ - -Dsechub.initialadmin.email=sechubadm@example.org \ - -Dsechub.initialadmin.apitoken="$ADMIN_APITOKEN" \ - -Dspring.servlet.multipart.max-file-size="$SECHUB_MAX_FILE_UPLOAD_SIZE" \ - -Dspring.servlet.multipart.max-request-size="$SECHUB_MAX_FILE_UPLOAD_SIZE" \ - -Dsechub.upload.binaries.maximum.bytes="$SECHUB_UPLOAD_BINARIES_MAXIMUM_BYTES" \ - -Dsechub.adapter.netsparker.userid=abc \ - -Dsechub.adapter.netsparker.apitoken=xyz \ - -Dsechub.adapter.netsparker.baseurl=https://example.org \ - -Dsechub.adapter.netsparker.defaultpolicyid=example \ - -Dsechub.adapter.netsparker.licenseid=example \ - -Dsechub.adapter.nessus.defaultpolicyid=example \ - -Dsechub.notification.email.administrators=example@example.org \ - -Dsechub.notification.email.from=example@example.org \ - -Dsechub.notification.smtp.hostname=example.org \ -Dserver.port=8443 \ -Dserver.address=0.0.0.0 \ -jar /sechub/sechub-server*.jar @@ -126,8 +133,11 @@ SecHub server settings: Starting up SecHub server EOF - java $java_debug_options $storage_options \ - -Dfile.encoding=UTF-8 -XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=80 -XshowSettings:vm \ + java $java_debug_options \ + -Dfile.encoding=UTF-8 \ + -XX:InitialRAMPercentage=50 \ + -XX:MaxRAMPercentage=80 \ + -XshowSettings:vm \ -jar /sechub/sechub-server*.jar } @@ -136,19 +146,18 @@ echo "Starting run script: run.sh $@" echo "Java version:" java --version -if [ "$JAVA_ENABLE_DEBUG" = "true" ] -then +if [ "$JAVA_ENABLE_DEBUG" = "true" ] ; then JAVA_DEBUG_PORT=15023 echo "# Opening port $JAVA_DEBUG_PORT for Java debugging" java_debug_options="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$JAVA_DEBUG_PORT" fi -if [ "$S3_ENABLED" = "true" ] -then - storage_options="-Dsechub.storage.s3.endpoint=$S3_ENDPOINT" - storage_options="$storage_options -Dsechub.storage.s3.bucketname=$S3_BUCKETNAME" - storage_options="$storage_options -Dsechub.storage.s3.accesskey=$S3_ACCESSKEY" - storage_options="$storage_options -Dsechub.storage.s3.secretkey=$S3_SECRETKEY" +if [ "$S3_ENABLED" = "true" ] ; then + # Set storage variables for Java Spring app: + export SECHUB_STORAGE_S3_ENDPOINT="$S3_ENDPOINT" + export SECHUB_STORAGE_S3_BUCKETNAME="$S3_BUCKETNAME" + export SECHUB_STORAGE_S3_ACCESSKEY="$S3_ACCESSKEY" + export SECHUB_STORAGE_S3_SECRETKEY="$S3_SECRETKEY" cat - < problems = visitor.getProblems(); - if (problems.isEmpty()) { - throw new IllegalStateException("testcase corrupt, did not found even expected test problem!"); - } - StringBuilder sb = new StringBuilder(); - for (DomainProblem problem : problems) { - if (problem.getFile().getName().endsWith("TestScanDomainUsesOtherDomain.java")) { - /* this is test case file to check problem is identified - we ignore this */ - continue; - } - sb.append(problem.getFile() + "\n" + problem.getProblem()); - sb.append("\n\n"); - } - if (sb.length() == 0) { - /* okay, only test problem found */ - return; - } - fail("Found problems:\n" + sb.toString()); - } -} diff --git a/sechub-test/src/test/java/com/mercedesbenz/sechub/test/encryption/EncryptionUsageTest.java b/sechub-test/src/test/java/com/mercedesbenz/sechub/test/encryption/EncryptionUsageTest.java new file mode 100644 index 0000000000..caf972757c --- /dev/null +++ b/sechub-test/src/test/java/com/mercedesbenz/sechub/test/encryption/EncryptionUsageTest.java @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.test.encryption; + +import static org.assertj.core.api.Assertions.*; + +import javax.crypto.AEADBadTagException; + +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.encryption.DefaultSecretKeyProvider; +import com.mercedesbenz.sechub.commons.encryption.EncryptionResult; +import com.mercedesbenz.sechub.commons.encryption.EncryptionRotationSetup; +import com.mercedesbenz.sechub.commons.encryption.EncryptionRotator; +import com.mercedesbenz.sechub.commons.encryption.EncryptionSupport; +import com.mercedesbenz.sechub.commons.encryption.InitializationVector; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipher; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherFactory; +import com.mercedesbenz.sechub.commons.encryption.PersistentCipherType; + +public class EncryptionUsageTest { + + @Test + void cipher_direct_usage() { + /* prepare */ + PersistentCipherType cipherType = PersistentCipherType.AES_GCM_SIV_256; + byte[] rawPlainTextInBytes = "hallo welt".getBytes(); + byte[] testSecret256bit = "x".repeat(32).getBytes(); + + /* execute - usage .. */ + PersistentCipherFactory factory = new PersistentCipherFactory(); + + // encrypt and simulate storage + PersistentCipher cipher = factory.createCipher(new DefaultSecretKeyProvider(testSecret256bit, cipherType), cipherType); + + InitializationVector initVector = cipher.createNewInitializationVector(); + byte[] encrypted = cipher.encrypt(rawPlainTextInBytes, initVector); + + // simulate storage + byte[] storedEncryptedDataBytes = encrypted; + byte[] storedInitVectorBytes = initVector.getInitializationBytes(); + + // decrypt + PersistentCipher anotherCipher = factory.createCipher(new DefaultSecretKeyProvider(testSecret256bit, cipherType), cipherType); + byte[] decrypted = anotherCipher.decrypt(storedEncryptedDataBytes, new InitializationVector(storedInitVectorBytes)); + + /* test */ + assertThat(decrypted).isEqualTo(rawPlainTextInBytes); + + } + + @Test + void encryption_support_usage_with_string() { /* prepare */ + PersistentCipherType cipherType = PersistentCipherType.AES_GCM_SIV_256; + String plainText = "hallo welt"; + byte[] testSecret256bit = "x".repeat(32).getBytes(); + + /* execute - usage .. */ + PersistentCipherFactory factory = new PersistentCipherFactory(); + + // encrypt and simulate storage + PersistentCipher cipher = factory.createCipher(new DefaultSecretKeyProvider(testSecret256bit, cipherType), cipherType); + + EncryptionSupport support = new EncryptionSupport(); + + EncryptionResult encryptionResult = support.encryptString(plainText, cipher); + + // simulate storage + byte[] storedEncryptedDataBytes = encryptionResult.getEncryptedData(); + byte[] storedInitVectorBytes = encryptionResult.getInitialVector().getInitializationBytes(); + + // decrypt + PersistentCipher anotherCipher = factory.createCipher(new DefaultSecretKeyProvider(testSecret256bit, cipherType), cipherType); + String decrypted = support.decryptString(storedEncryptedDataBytes, anotherCipher, new InitializationVector(storedInitVectorBytes)); + + /* test */ + assertThat(decrypted).isEqualTo(plainText); + + } + + @Test + void rotation_with_changed_password_same_init_vector() { + /* prepare */ + String testData = "hallo welt"; + byte[] oldPassword = "x".repeat(32).getBytes(); + byte[] newPassword = "y".repeat(32).getBytes(); + + PersistentCipherFactory factory = new PersistentCipherFactory(); + PersistentCipherType cipherType = PersistentCipherType.AES_GCM_SIV_256; + PersistentCipher cipherOldPassword = factory.createCipher(new DefaultSecretKeyProvider(oldPassword, cipherType), cipherType); + + EncryptionSupport encryptSupport = new EncryptionSupport(); + + // encrypt with old cipher + EncryptionResult encryptionResult = encryptSupport.encryptString(testData, cipherOldPassword); + + PersistentCipher cipherNewPassword = factory.createCipher(new DefaultSecretKeyProvider(newPassword, cipherType), cipherType); + + EncryptionRotator rotatorToTest = new EncryptionRotator(); + + /* test */ + + /* @formatter:off */ + EncryptionRotationSetup rotateSetup = EncryptionRotationSetup.builder(). + newCipher(cipherNewPassword). + oldCipher(cipherOldPassword). + oldInitialVector(encryptionResult.getInitialVector()). + build(); + + byte[] newEncrypted = rotatorToTest.rotate(encryptionResult.getEncryptedData(), rotateSetup); + + /* test */ + + /* @formatter:on */ + String unencryptedTestData = encryptSupport.decryptString(newEncrypted, cipherNewPassword, rotateSetup.getNewInitialVector()); + + assertThat(testData).isEqualTo(unencryptedTestData); + + } + + @Test + void rotation_with_changed_password_different_init_vector() { + /* prepare */ + String testData = "hallo welt"; + byte[] oldPassword = "x".repeat(32).getBytes(); + byte[] newPassword = "y".repeat(32).getBytes(); + + PersistentCipherFactory factory = new PersistentCipherFactory(); + PersistentCipherType cipherType = PersistentCipherType.AES_GCM_SIV_256; + PersistentCipher cipherOldPassword = factory.createCipher(new DefaultSecretKeyProvider(oldPassword, cipherType), cipherType); + + EncryptionSupport encryptSupport = new EncryptionSupport(); + + // encrypt with old cipher + EncryptionResult firstEncryptionResult = encryptSupport.encryptString(testData, cipherOldPassword); + + PersistentCipher cipherNewPassword = factory.createCipher(new DefaultSecretKeyProvider(newPassword, cipherType), cipherType); + InitializationVector newInitVector = cipherNewPassword.createNewInitializationVector(); + + EncryptionRotator rotatorToTest = new EncryptionRotator(); + + /* test */ + + /* @formatter:off */ + EncryptionRotationSetup rotateSetup = EncryptionRotationSetup.builder(). + newCipher(cipherNewPassword). + oldCipher(cipherOldPassword). + oldInitialVector(firstEncryptionResult.getInitialVector()). + newInitialVector(newInitVector). + build(); + + byte[] newEncrypted = rotatorToTest.rotate(firstEncryptionResult.getEncryptedData(), rotateSetup); + + /* test */ + + /* @formatter:on */ + String unencryptedTestData = encryptSupport.decryptString(newEncrypted, cipherNewPassword, newInitVector); + + assertThat(testData).isEqualTo(unencryptedTestData); + + // additional test - we check that the decryption with old init vector does not + // work + assertThatThrownBy(() -> encryptSupport.decryptString(newEncrypted, cipherNewPassword, firstEncryptionResult.getInitialVector())) + .isInstanceOf(IllegalStateException.class).hasRootCauseInstanceOf(AEADBadTagException.class); + } +} diff --git a/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ReportTestHelper.java b/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ReportTestHelper.java index c5935a0604..6509cf0d25 100644 --- a/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ReportTestHelper.java +++ b/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ReportTestHelper.java @@ -39,19 +39,19 @@ public class ReportTestHelper { private static final Logger LOG = LoggerFactory.getLogger(ReportTestHelper.class); public static String load3rdPartyReportAsString(String fullName) { - return TestFileReader.loadTextFile(new File(REPORT_PATH + "input/" + fullName)); + return TestFileReader.readTextFromFile(new File(REPORT_PATH + "input/" + fullName)); } public static String loadSarifReport(String name) { - return TestFileReader.loadTextFile(new File(REPORT_PATH + "input/" + name + ".sarif.json")); + return TestFileReader.readTextFromFile(new File(REPORT_PATH + "input/" + name + ".sarif.json")); } public static String loadSecHubReportFileTemplate(String name) { - return TestFileReader.loadTextFile(new File(REPORT_PATH + "input/" + name + ".sechub-template.json")); + return TestFileReader.readTextFromFile(new File(REPORT_PATH + "input/" + name + ".sechub-template.json")); } public static String loadExpectedSecHubReportOutputFile(String name) { - return TestFileReader.loadTextFile(new File(REPORT_PATH + "output/" + name + ".sechub.json")); + return TestFileReader.readTextFromFile(new File(REPORT_PATH + "output/" + name + ".sechub.json")); } public static String transformSarifToSecHubReportJSON(String sarifJson, ProductIdentifier productIdentifier, String sechubJobUUID) diff --git a/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ThymeLeafHTMLReportingTest.java b/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ThymeLeafHTMLReportingTest.java index 957746aa52..82de29c04a 100644 --- a/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ThymeLeafHTMLReportingTest.java +++ b/sechub-test/src/test/java/com/mercedesbenz/sechub/test/report/ThymeLeafHTMLReportingTest.java @@ -27,7 +27,7 @@ import com.mercedesbenz.sechub.commons.model.SecHubReportMetaData; import com.mercedesbenz.sechub.commons.model.SecHubReportModel; import com.mercedesbenz.sechub.commons.model.TrafficLightSupport; -import com.mercedesbenz.sechub.docgen.util.TextFileWriter; +import com.mercedesbenz.sechub.docgen.util.DocGenTextFileWriter; import com.mercedesbenz.sechub.domain.scan.SecHubExecutionException; import com.mercedesbenz.sechub.domain.scan.report.HTMLScanResultReportModelBuilder; import com.mercedesbenz.sechub.domain.scan.report.ScanReport; @@ -256,7 +256,7 @@ void example8_pseudo_gitleaks_sarif_with_version_control_transformed_to_sechub_r private void storeHTMLOutputAsFile(String htmlResult, String name) throws IOException { TestFileWriter writer = new TestFileWriter(); - writer.save(new File("./build/test-data/thymeleaf-test/" + name + ".html"), htmlResult, true); + writer.writeTextToFile(new File("./build/test-data/thymeleaf-test/" + name + ".html"), htmlResult, true); } private String processThymeLeafTemplates(TestReportContext context) throws IOException, SecHubExecutionException { @@ -280,8 +280,8 @@ private void storeAsFileForDevelopmentWhenTempFilesAreKept(String content, TestR return; } Path testFile = TestUtil.createTempFileInBuildFolder("thymeleaf-html-reporttest-" + context.exampleName + "." + fileEnding); - TextFileWriter writer = new TextFileWriter(); - writer.save(testFile.toFile(), content); + DocGenTextFileWriter writer = new DocGenTextFileWriter(); + writer.writeTextToFile(testFile.toFile(), content); LOG.info("Wrote test file to:{}", testFile); } diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/CSSFileToFragementMerger.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/CSSFileToFragementMerger.java index 4203c0be11..90695cada1 100644 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/CSSFileToFragementMerger.java +++ b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/CSSFileToFragementMerger.java @@ -16,8 +16,8 @@ public class CSSFileToFragementMerger { private static final TestFileWriter writer = new TestFileWriter(); public void merge(File cssFile, File fragmentsFile) throws IOException { - String cssContent = TestFileReader.loadTextFile(cssFile); - String fragmentsContent = TestFileReader.loadTextFile(fragmentsFile); + String cssContent = TestFileReader.readTextFromFile(cssFile); + String fragmentsContent = TestFileReader.readTextFromFile(fragmentsFile); int fragmentIndex = fragmentsContent.indexOf(CSS_FRAGMENT_START); @@ -31,7 +31,7 @@ public void merge(File cssFile, File fragmentsFile) throws IOException { } String newFragmentContent = fragmentsContent.substring(0, fragmentIndex) + "\n" + cssContent + "\n" + fragmentsContent.substring(closeStyleIndex); - writer.save(fragmentsFile, newFragmentContent, true); + writer.writeTextToFile(fragmentsFile, newFragmentContent, true); LOG.info("Updated fragment file: {}\nfrom with CSS content of: {}", fragmentsFile, cssFile); } diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/DomainAccessSourceVisitor.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/DomainAccessSourceVisitor.java deleted file mode 100644 index 5db583a76f..0000000000 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/DomainAccessSourceVisitor.java +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.test; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import com.mercedesbenz.sechub.test.SimpleFileBasedPackageScanner.PackageSourceVisitor; - -public class DomainAccessSourceVisitor implements PackageSourceVisitor { - - private static final String COM_MERCEDESBENZ_SECHUB_DOMAIN = "com.mercedesbenz.sechub.domain."; - - public class DomainProblem { - private File file; - private String problem; - - public DomainProblem(File sourceFile, String string) { - this.file = sourceFile; - this.problem = string; - } - - public File getFile() { - return file; - } - - public String getProblem() { - return problem; - } - - } - - private List problems; - - public List getProblems() { - return problems; - } - - public DomainAccessSourceVisitor() { - problems = new ArrayList<>(); - } - - @Override - public void visit(File sourceFile, String packageOfFile, List importedPackages) { - if (sourceFile.getName().endsWith("Test.java") && sourceFile.getAbsolutePath().contains("sechub-test")) { - // inside sechub-test project we allow this inside tests - e.g. when we test - // that constants are same between two different domains (means defined twice). - return; - } - String packageDomainName = fetchDomainName(packageOfFile); - if (packageDomainName == null || packageDomainName.isEmpty()) { - return; - } - for (String imported : importedPackages) { - String importDomainName = fetchDomainName(imported); - if (importDomainName != null && !importDomainName.equals(packageDomainName)) { - problems.add(new DomainProblem(sourceFile, "import problem:" + imported + " imported but inside package:" + packageOfFile)); - } - } - } - - private String fetchDomainName(String packageOfFile) { - int index = packageOfFile.indexOf(COM_MERCEDESBENZ_SECHUB_DOMAIN); - if (index == -1) { - return null; - } - String result = packageOfFile.substring(index + COM_MERCEDESBENZ_SECHUB_DOMAIN.length()); - int indexPoint = result.indexOf('.'); - if (indexPoint != -1) { - result = result.substring(0, indexPoint); - } - return result; - } - -} \ No newline at end of file diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java index 190fd95fbe..942266d5ff 100644 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java +++ b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java @@ -445,6 +445,14 @@ public String buildAdminFetchesAutoCleanupConfigurationUrl() { return buildUrl(API_ADMIN_CONFIG, "autoclean"); } + public String buildAdminStartsEncryptionRotation() { + return buildUrl(API_ADMIN, "encryption/rotate"); + } + + public String buildAdminFetchesEncryptionStatus() { + return buildUrl(API_ADMIN, "encryption/status"); + } + /* +-----------------------------------------------------------------------+ */ /* +............................ integration test special (anonymous) .....+ */ /* +-----------------------------------------------------------------------+ */ @@ -642,7 +650,6 @@ public String buildIntegrationTestFetchFullScandata(UUID sechubJobUIUD) { } // statistic parts - public String buildintegrationTestFetchJobStatistic(UUID sechubJobUUID) { return buildUrl(API_ANONYMOUS, "integrationtest/statistic/job/" + sechubJobUUID); } @@ -659,4 +666,12 @@ public String buildintegrationTestFetchJobRunStatisticData(UUID sechubJobUUID) { return buildUrl(API_ANONYMOUS, "integrationtest/statistic/job-run-data/" + sechubJobUUID); } + public String buildIntegrationTestFetchScheduleEncryptionPoolIdForSecHubJob(UUID sechubJobUUID) { + return buildUrl(API_ANONYMOUS, "integrationtest/schedule/encryption-pool-id/job/" + sechubJobUUID.toString()); + } + + public String buildIntegrationTestStartScheduleCipherPoolDataCleanup() { + return buildUrl(API_ANONYMOUS, "integrationtest/schedule/cipher-pool-data/cleanup"); + } + } diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SimpleFileBasedPackageScanner.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SimpleFileBasedPackageScanner.java deleted file mode 100644 index 03bf6d0362..0000000000 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SimpleFileBasedPackageScanner.java +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.test; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; - -public class SimpleFileBasedPackageScanner { - private static final String PACKAGE = "package"; - private static final String IMPORT = "import"; - private static final JavaFileFiter FILE_FILTER = new JavaFileFiter(); - - private static class JavaFileFiter implements FileFilter { - - @Override - public boolean accept(File file) { - if (file.isDirectory()) { - return true; - } - String name = file.getName(); - return name.endsWith(".java"); - } - } - - public static interface PackageSourceVisitor { - - public void visit(File sourceFile, String packageOfFile, List importedPackages); - } - - private boolean verbose; - - public void visit(File file, PackageSourceVisitor visitor) { - if (file.isDirectory()) { - if (verbose) { - System.out.println("visiting directory:" + file); - } - for (File child : file.listFiles(FILE_FILTER)) { - visit(child, visitor); - } - } else { - if (verbose) { - System.out.println("visiting file:" + file); - } - loadAndVisitFile(file, visitor); - } - } - - private void loadAndVisitFile(File file, PackageSourceVisitor visitor) { - List imports = new ArrayList<>(); - - String packageOfFile = ""; - try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { - String line = null; - while ((line = br.readLine()) != null) { - String trimmedLine = line.trim(); - if (trimmedLine.startsWith(IMPORT)) { - String importLine = reduceLineWithoutPrefixAndTrailingSemicolon(trimmedLine, IMPORT); - imports.add(importLine); - } else if (trimmedLine.startsWith(PACKAGE)) { - packageOfFile = reduceLineWithoutPrefixAndTrailingSemicolon(trimmedLine, PACKAGE); - } - } - } catch (Exception e) { - throw new IllegalStateException("Something corrupt: Cannot read file " + file.getAbsolutePath(), e); - } - visitor.visit(file, packageOfFile, imports); - } - - private String reduceLineWithoutPrefixAndTrailingSemicolon(String trimmedLine, String prefix) { - String reduced = trimmedLine.substring(prefix.length()).trim(); - /* remove ; */ - if (reduced.endsWith(";")) { - reduced = reduced.substring(0, reduced.length() - 1); - } - return reduced.trim(); - } - - public void setVerbose(boolean verbose) { - this.verbose = verbose; - }; -} \ No newline at end of file diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileReader.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileReader.java index e0365087c5..adf6553d27 100644 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileReader.java +++ b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileReader.java @@ -10,23 +10,30 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * A class to read test files. Will always use UTF-8 for text files. This class + * may only be used by tests! + * + * @author Albert Tregnaghi + * + */ public class TestFileReader { private static final Logger LOG = LoggerFactory.getLogger(TestFileReader.class); - public static String loadTextFile(Path pathToFile) { - return loadTextFile(pathToFile.toFile()); + public static String readTextFromFile(Path pathToFile) { + return readTextFromFile(pathToFile.toFile()); } - public static String loadTextFile(String pathToFile) { - return loadTextFile(new File(pathToFile)); + public static String readTextFromFile(String pathToFile) { + return readTextFromFile(new File(pathToFile)); } - public static String loadTextFile(File file) { - return loadTextFile(file, "\n"); + public static String readTextFromFile(File file) { + return readTextFromFile(file, "\n"); } - public static String loadTextFile(File file, String lineBreak) { + public static String readTextFromFile(File file, String lineBreak) { if (TestUtil.isTraceEnabled()) { LOG.info("Loading file:{}", file); } diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileWriter.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileWriter.java index f8aa266735..2a3ef988ff 100644 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileWriter.java +++ b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/TestFileWriter.java @@ -11,27 +11,31 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * A class to write test files. Will always use UTF-8 for text files. This class + * may only be used by tests! + * + * @author Albert Tregnaghi + * + */ public class TestFileWriter { + private static final Charset CHARSET_UTF_8 = Charset.forName("UTF-8"); private static final Logger LOG = LoggerFactory.getLogger(TestFileWriter.class); public void write(File targetFile, String text) throws IOException { - save(targetFile, text, false); + writeTextToFile(targetFile, text, false); } - public void save(String text, File targetFile, Charset charset) throws IOException { - internalSave(targetFile, text, true, charset); + public void writeTextToFile(String text, File targetFile, Charset charset) throws IOException { + internalWriteTextToFile(targetFile, text, true, charset); } - public void write(File targetFile, String text, boolean overwrite) throws IOException { - save(targetFile, text, overwrite); + public void writeTextToFile(File targetFile, String text, boolean overwrite) throws IOException { + internalWriteTextToFile(targetFile, text, overwrite, CHARSET_UTF_8); } - public void save(File targetFile, String text, boolean overwrite) throws IOException { - internalSave(targetFile, text, overwrite, Charset.forName("UTF-8")); - } - - private void internalSave(File targetFile, String text, boolean overwrite, Charset charset) throws IOException { + private void internalWriteTextToFile(File targetFile, String text, boolean overwrite, Charset charset) throws IOException { if (targetFile == null) { throw new IllegalArgumentException("null not allowed as file!"); } diff --git a/sechub-testframework/src/test/java/com/mercedesbenz/sechub/test/DomainAccessSourceVisitorTest.java b/sechub-testframework/src/test/java/com/mercedesbenz/sechub/test/DomainAccessSourceVisitorTest.java deleted file mode 100644 index 9968d109fc..0000000000 --- a/sechub-testframework/src/test/java/com/mercedesbenz/sechub/test/DomainAccessSourceVisitorTest.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.test; - -import static org.junit.Assert.*; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; - -public class DomainAccessSourceVisitorTest { - - private DomainAccessSourceVisitor visitorToTest; - private List packages; - private File sourceFile; - - @Test - public void same_domains_no_problem() { - /* execute */ - visitorToTest.visit(sourceFile, "com.mercedesbenz.sechub.domain.alpha", packages); - - /* test */ - assertTrue(visitorToTest.getProblems().isEmpty()); - - } - - @Test - public void different_domains_2_problem_when_2_imports() { - /* execute */ - visitorToTest.visit(sourceFile, "com.mercedesbenz.sechub.domain.beta", packages); - - /* test */ - assertEquals(2, visitorToTest.getProblems().size()); - - } - - @Before - public void before() { - packages = new ArrayList<>(); - sourceFile = new File("."); - - packages.add("com.mercedesbenz.sechub.domain.alpha.centauri"); - packages.add("com.mercedesbenz.sechub.domain.alpha.else"); - - visitorToTest = new DomainAccessSourceVisitor(); - } - -} diff --git a/sechub-website/package-lock.json b/sechub-website/package-lock.json index 44030fbe1c..b7b5327dd2 100644 --- a/sechub-website/package-lock.json +++ b/sechub-website/package-lock.json @@ -13,7 +13,7 @@ "@heroicons/vue": "^2.1.1", "@nuxt/devtools": "*", "@nuxtjs/tailwindcss": "^6.11.4", - "nuxt": "^3.10.3", + "nuxt": "^3.12.4", "nuxt-security": "^1.2.1", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11" @@ -45,52 +45,52 @@ } }, "node_modules/@antfu/utils": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz", - "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==", + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -115,14 +115,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -130,26 +130,26 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -167,19 +167,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.0.tgz", - "integrity": "sha512-QAH+vfvts51BCsNZ2PhY6HAggnlS6omLLFTsIpeqZk/MmJ6cW7tgz5yRv0fMJThcr6FmbMrENh1RgrWPTYA76g==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.20", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", "semver": "^6.3.1" }, "engines": { @@ -198,75 +196,42 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -276,35 +241,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", - "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.22.15", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -314,101 +279,94 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", + "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -417,14 +375,14 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.0.tgz", - "integrity": "sha512-LiT1RqZWeij7X+wGxCoYh3/3b8nVOX6/7BZ9wiQgAIyjoeQWdROaodJCgT+dwtbjHaz0r7bEbHJzjSbVfcOyjQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.7.tgz", + "integrity": "sha512-RL9GR0pUG5Kc8BUWLNDm2T5OpYwSX15r98I0IkgmRQTXuELq/OynH8xtMTMvTJFjXbMWFVTKtYkTaYQsuAwQlQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.0", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-decorators": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-decorators": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -434,12 +392,12 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.0.tgz", - "integrity": "sha512-MXW3pQCu9gUiVGzqkGqsgiINDVYXoAnrY8FYF/rmb+OfufNF0zHMpHPN4ulRrinxYT8Vk/aZJxYqOKsDECjKAw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.7.tgz", + "integrity": "sha512-Ui4uLJJrRV1lb38zg1yYTmRKmiZLiftDEvZN2iq3kd9kUFU+PttmzTbAFC2ucRk/XJmtek6G23gPsuZbhrT8fQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -449,12 +407,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -491,12 +449,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -506,15 +464,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", - "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.23.6", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-typescript": "^7.23.3" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -533,33 +492,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -568,13 +524,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -582,12 +538,15 @@ } }, "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz", - "integrity": "sha512-lKN2XCfKCmpKb86a1tl4GIwsJYDy9TGuwjhDELLmpKygQhw8X2xR4dusgpC5Tg7q1pB96Eb0rBo81kxSILQMwA==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", + "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", "dev": true, "dependencies": { "mime": "^3.0.0" + }, + "engines": { + "node": ">=16.13" } }, "node_modules/@cloudflare/kv-asset-handler/node_modules/mime": { @@ -711,9 +670,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", - "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], @@ -727,9 +686,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", - "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -743,9 +702,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", - "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -759,9 +718,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", - "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -775,9 +734,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", - "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -791,9 +750,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", - "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -807,9 +766,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", - "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -823,9 +782,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", - "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -839,9 +798,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", - "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -855,9 +814,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", - "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -871,9 +830,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", - "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -887,9 +846,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", - "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -903,9 +862,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", - "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -919,9 +878,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", - "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -935,9 +894,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", - "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -951,9 +910,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", - "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -967,9 +926,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", - "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -983,9 +942,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", - "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -998,10 +957,26 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", - "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -1015,9 +990,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", - "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -1031,9 +1006,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", - "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -1047,9 +1022,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", - "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -1063,9 +1038,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", - "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -1256,15 +1231,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.24.tgz", - "integrity": "sha512-+VaWXDa6+l6MhflBvVXjIEAzb59nQ2JUK3bwRp2zRpPtU+8TFRy9Gg/5oIcNlkEL5PGlBFGfemUVvIgLnTzq7Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1323,12 +1298,12 @@ } }, "node_modules/@netlify/functions": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.6.0.tgz", - "integrity": "sha512-vU20tij0fb4nRGACqb+5SQvKd50JYyTyEhQetCMHdakcJFzjLDivvRR16u1G2Oy4A7xNAtGJF1uz8reeOtTVcQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.8.1.tgz", + "integrity": "sha512-+6wtYdoz0yE06dSa9XkP47tw5zm6g13QMeCwM3MmHx1vn8hzwFa51JtmfraprdkL7amvb7gaNM+OOhQU1h6T8A==", "dev": true, "dependencies": { - "@netlify/serverless-functions-api": "1.14.0" + "@netlify/serverless-functions-api": "1.19.1" }, "engines": { "node": ">=14.0.0" @@ -1344,16 +1319,16 @@ } }, "node_modules/@netlify/serverless-functions-api": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.14.0.tgz", - "integrity": "sha512-HUNETLNvNiC2J+SB/YuRwJA9+agPrc0azSoWVk8H85GC+YE114hcS5JW+dstpKwVerp2xILE3vNWN7IMXP5Q5Q==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.19.1.tgz", + "integrity": "sha512-2KYkyluThg1AKfd0JWI7FzpS4A/fzVVGYIf6AM4ydWyNj8eI/86GQVLeRgDoH7CNOxt243R5tutWlmHpVq0/Ew==", "dev": true, "dependencies": { "@netlify/node-cookies": "^0.1.0", "urlpattern-polyfill": "8.0.2" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@nodelib/fs.scandir": { @@ -1391,498 +1366,632 @@ "node": ">= 8" } }, - "node_modules/@npmcli/agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.1.tgz", - "integrity": "sha512-H4FrOVtNyWC8MUwL3UfjOsAihHvT1Pe8POj3JvjXhSTJipsZMtgUALCT4mGyYZNxymkUfOw3PUj6dE4QPp6osQ==", + "node_modules/@nuxt/devalue": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nuxt/devalue/-/devalue-2.0.2.tgz", + "integrity": "sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==", + "dev": true + }, + "node_modules/@nuxt/devtools": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@nuxt/devtools/-/devtools-1.3.9.tgz", + "integrity": "sha512-tFKlbUPgSXw4tyD8xpztQtJeVn3egdKbFCV0xc92FbfGbclAyaa3XhKA2tMWXEGZQpykAWMRNrGWN24FtXFA6Q==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.10", + "@nuxt/devtools-kit": "1.3.9", + "@nuxt/devtools-wizard": "1.3.9", + "@nuxt/kit": "^3.12.2", + "@vue/devtools-core": "7.3.3", + "@vue/devtools-kit": "7.3.3", + "birpc": "^0.2.17", + "consola": "^3.2.3", + "cronstrue": "^2.50.0", + "destr": "^2.0.3", + "error-stack-parser-es": "^0.1.4", + "execa": "^7.2.0", + "fast-glob": "^3.3.2", + "fast-npm-meta": "^0.1.1", + "flatted": "^3.3.1", + "get-port-please": "^3.1.2", + "hookable": "^5.5.3", + "image-meta": "^0.2.0", + "is-installed-globally": "^1.0.0", + "launch-editor": "^2.8.0", + "local-pkg": "^0.5.0", + "magicast": "^0.3.4", + "nypm": "^0.3.9", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.1.2", + "rc9": "^2.1.2", + "scule": "^1.3.0", + "semver": "^7.6.2", + "simple-git": "^3.25.0", + "sirv": "^2.0.4", + "unimport": "^3.7.2", + "vite-plugin-inspect": "^0.8.4", + "vite-plugin-vue-inspector": "^5.1.2", + "which": "^3.0.1", + "ws": "^8.17.1" + }, + "bin": { + "devtools": "cli.mjs" + }, + "peerDependencies": { + "vite": "*" + } + }, + "node_modules/@nuxt/devtools-kit": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-1.3.9.tgz", + "integrity": "sha512-tgr/F+4BbI53/JxgaXl3cuV9dMuCXMsd4GEXN+JqtCdAkDbH3wL79GGWx0/6I9acGzRsB6UZ1H6U96nfgcIrAw==", "dev": true, "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.1" + "@nuxt/kit": "^3.12.2", + "@nuxt/schema": "^3.12.3", + "execa": "^7.2.0" }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "peerDependencies": { + "vite": "*" } }, - "node_modules/@npmcli/agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "node_modules/@nuxt/devtools-wizard": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-wizard/-/devtools-wizard-1.3.9.tgz", + "integrity": "sha512-WMgwWWuyng+Y6k7sfBI95wYnec8TPFkuYbHHOlYQgqE9dAewPisSbEm3WkB7p/W9UqxpN8mvKN5qUg4sTmEpgQ==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "consola": "^3.2.3", + "diff": "^5.2.0", + "execa": "^7.2.0", + "global-directory": "^4.0.1", + "magicast": "^0.3.4", + "pathe": "^1.1.2", + "pkg-types": "^1.1.2", + "prompts": "^2.4.2", + "rc9": "^2.1.2", + "semver": "^7.6.2" }, - "engines": { - "node": ">= 14" + "bin": { + "devtools-wizard": "cli.mjs" } }, - "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "node_modules/@nuxt/kit": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.12.4.tgz", + "integrity": "sha512-aNRD1ylzijY0oYolldNcZJXVyxdGzNTl+Xd0UYyFQCu9f4wqUZqQ9l+b7arCEzchr96pMK0xdpvLcS3xo1wDcw==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "@nuxt/schema": "3.12.4", + "c12": "^1.11.1", + "consola": "^3.2.3", + "defu": "^6.1.4", + "destr": "^2.0.3", + "globby": "^14.0.2", + "hash-sum": "^2.0.0", + "ignore": "^5.3.1", + "jiti": "^1.21.6", + "klona": "^2.0.6", + "knitwork": "^1.1.0", + "mlly": "^1.7.1", + "pathe": "^1.1.2", + "pkg-types": "^1.1.3", + "scule": "^1.3.0", + "semver": "^7.6.3", + "ufo": "^1.5.4", + "unctx": "^2.3.1", + "unimport": "^3.9.0", + "untyped": "^1.4.2" }, "engines": { - "node": ">= 14" + "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "node_modules/@nuxt/schema": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.12.4.tgz", + "integrity": "sha512-H7FwBV4ChssMaeiLyPdVLOLUa0326ebp3pNbJfGgFt7rSoKh1MmgjorecA8JMxOQZziy3w6EELf4+5cgLh/F1w==", "dev": true, + "dependencies": { + "compatx": "^0.1.8", + "consola": "^3.2.3", + "defu": "^6.1.4", + "hookable": "^5.5.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.3", + "scule": "^1.3.0", + "std-env": "^3.7.0", + "ufo": "^1.5.4", + "uncrypto": "^0.1.3", + "unimport": "^3.9.0", + "untyped": "^1.4.2" + }, "engines": { - "node": "14 || >=16.14" + "node": "^14.18.0 || >=16.10.0" } }, - "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "node_modules/@nuxt/telemetry": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@nuxt/telemetry/-/telemetry-2.5.4.tgz", + "integrity": "sha512-KH6wxzsNys69daSO0xUv0LEBAfhwwjK1M+0Cdi1/vxmifCslMIY7lN11B4eywSfscbyVPAYJvANyc7XiVPImBQ==", "dev": true, "dependencies": { - "semver": "^7.3.5" + "@nuxt/kit": "^3.11.2", + "ci-info": "^4.0.0", + "consola": "^3.2.3", + "create-require": "^1.1.1", + "defu": "^6.1.4", + "destr": "^2.0.3", + "dotenv": "^16.4.5", + "git-url-parse": "^14.0.0", + "is-docker": "^3.0.0", + "jiti": "^1.21.0", + "mri": "^1.2.0", + "nanoid": "^5.0.7", + "ofetch": "^1.3.4", + "parse-git-config": "^3.0.0", + "pathe": "^1.1.2", + "rc9": "^2.1.2", + "std-env": "^3.7.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "bin": { + "nuxt-telemetry": "bin/nuxt-telemetry.mjs" } }, - "node_modules/@npmcli/git": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.4.tgz", - "integrity": "sha512-nr6/WezNzuYUppzXRaYu/W4aT5rLxdXqEFupbh6e/ovlYFQ8hpu1UUPV3Ir/YTl+74iXl2ZOMlGzudh9ZPUchQ==", + "node_modules/@nuxt/vite-builder": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.12.4.tgz", + "integrity": "sha512-5v3y6SkshJurZYJWHtc7+NGeCgptsreCSguBCZVzJxYdsPFdMicLoxjTt8IGAHWjkGVONrX+K8NBSFFgnx40jQ==", "dev": true, "dependencies": { - "@npmcli/promise-spawn": "^7.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^9.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^4.0.0" + "@nuxt/kit": "3.12.4", + "@rollup/plugin-replace": "^5.0.7", + "@vitejs/plugin-vue": "^5.0.5", + "@vitejs/plugin-vue-jsx": "^4.0.0", + "autoprefixer": "^10.4.19", + "clear": "^0.1.0", + "consola": "^3.2.3", + "cssnano": "^7.0.4", + "defu": "^6.1.4", + "esbuild": "^0.23.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "externality": "^1.0.2", + "get-port-please": "^3.1.2", + "h3": "^1.12.0", + "knitwork": "^1.1.0", + "magic-string": "^0.30.10", + "mlly": "^1.7.1", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.1.3", + "postcss": "^8.4.39", + "rollup-plugin-visualizer": "^5.12.0", + "std-env": "^3.7.0", + "strip-literal": "^2.1.0", + "ufo": "^1.5.4", + "unenv": "^1.10.0", + "unplugin": "^1.11.0", + "vite": "^5.3.4", + "vite-node": "^2.0.3", + "vite-plugin-checker": "^0.7.2", + "vue-bundle-renderer": "^2.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^14.18.0 || >=16.10.0" + }, + "peerDependencies": { + "vue": "^3.3.4" } }, - "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], "dev": true, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "14 || >=16.14" + "node": ">=18" } }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", - "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "lib/index.js" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@npmcli/package-json": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.0.0.tgz", - "integrity": "sha512-OI2zdYBLhQ7kpNPaJxiflofYIpkNLi+lnGdzqUOfRmCF3r2l1nadcjtCYMJKv/Utm/ZtlffaUuTiAktPHbc17g==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^7.0.0", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.5.3" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=18" } }, - "node_modules/@npmcli/package-json/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=18" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.1.tgz", - "integrity": "sha512-P4KkF9jX3y+7yFUxgcUdDtLy+t4OlDGuEBLNs57AZsfSfg+uV6MLndqGpnl4831ggaEdXwR50XFoZP4VFtHolg==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "which": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@npmcli/run-script": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", - "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.0", - "node-gyp": "^10.0.0", - "which": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@nuxt/devalue": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nuxt/devalue/-/devalue-2.0.2.tgz", - "integrity": "sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==", - "dev": true + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@nuxt/devtools": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@nuxt/devtools/-/devtools-1.0.8.tgz", - "integrity": "sha512-o6aBFEBxc8OgVHV4OPe2g0q9tFIe9HiTxRiJnlTJ+jHvOQsBLS651ArdVtwLChf9UdMouFlpLLJ1HteZqTbtsQ==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "@antfu/utils": "^0.7.7", - "@nuxt/devtools-kit": "1.0.8", - "@nuxt/devtools-wizard": "1.0.8", - "@nuxt/kit": "^3.9.1", - "birpc": "^0.2.14", - "consola": "^3.2.3", - "destr": "^2.0.2", - "error-stack-parser-es": "^0.1.1", - "execa": "^7.2.0", - "fast-glob": "^3.3.2", - "flatted": "^3.2.9", - "get-port-please": "^3.1.2", - "hookable": "^5.5.3", - "image-meta": "^0.2.0", - "is-installed-globally": "^1.0.0", - "launch-editor": "^2.6.1", - "local-pkg": "^0.5.0", - "magicast": "^0.3.2", - "nypm": "^0.3.4", - "ohash": "^1.1.3", - "pacote": "^17.0.5", - "pathe": "^1.1.2", - "perfect-debounce": "^1.0.0", - "pkg-types": "^1.0.3", - "rc9": "^2.1.1", - "scule": "^1.2.0", - "semver": "^7.5.4", - "simple-git": "^3.22.0", - "sirv": "^2.0.4", - "unimport": "^3.7.1", - "vite-plugin-inspect": "^0.8.1", - "vite-plugin-vue-inspector": "^4.0.2", - "which": "^3.0.1", - "ws": "^8.16.0" - }, - "bin": { - "devtools": "cli.mjs" - }, - "peerDependencies": { - "nuxt": "^3.9.0", - "vite": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@nuxt/devtools-kit": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-1.0.8.tgz", - "integrity": "sha512-j7bNZmoAXQ1a8qv6j6zk4c/aekrxYqYVQM21o/Hy4XHCUq4fajSgpoc8mjyWJSTfpkOmuLyEzMexpDWiIVSr6A==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nuxt/kit": "^3.9.1", - "@nuxt/schema": "^3.9.1", - "execa": "^7.2.0" - }, - "peerDependencies": { - "nuxt": "^3.9.0", - "vite": "*" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@nuxt/devtools-wizard": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@nuxt/devtools-wizard/-/devtools-wizard-1.0.8.tgz", - "integrity": "sha512-RxyOlM7Isk5npwXwDJ/rjm9ekX5sTNG0LS0VOBMdSx+D5nlRPMRr/r9yO+9WQDyzPLClLzHaXRHBWLPlRX3IMw==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "consola": "^3.2.3", - "diff": "^5.1.0", - "execa": "^7.2.0", - "global-directory": "^4.0.1", - "magicast": "^0.3.2", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "prompts": "^2.4.2", - "rc9": "^2.1.1", - "semver": "^7.5.4" - }, - "bin": { - "devtools-wizard": "cli.mjs" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@nuxt/kit": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.10.3.tgz", - "integrity": "sha512-PUjYB9Mvx0qD9H1QZBwwtY4fLlCLET+Mm9BVqUOtXCaGoXd6u6BE4e/dGFPk2UEKkIcDGrUMSbqkHYvsEuK9NQ==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nuxt/schema": "3.10.3", - "c12": "^1.9.0", - "consola": "^3.2.3", - "defu": "^6.1.4", - "globby": "^14.0.1", - "hash-sum": "^2.0.0", - "ignore": "^5.3.1", - "jiti": "^1.21.0", - "knitwork": "^1.0.0", - "mlly": "^1.6.0", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "scule": "^1.3.0", - "semver": "^7.6.0", - "ufo": "^1.4.0", - "unctx": "^2.3.1", - "unimport": "^3.7.1", - "untyped": "^1.4.2" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": ">=18" } }, - "node_modules/@nuxt/schema": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.10.3.tgz", - "integrity": "sha512-a4cYbeskEVBPazgAhvUGkL/j7ho/iPWMK3vCEm6dRMjSqHVEITRosrj0aMfLbRrDpTrMjlRs0ZitxiaUfE/p5Q==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nuxt/ui-templates": "^1.3.1", - "consola": "^3.2.3", - "defu": "^6.1.4", - "hookable": "^5.5.3", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "scule": "^1.3.0", - "std-env": "^3.7.0", - "ufo": "^1.4.0", - "unimport": "^3.7.1", - "untyped": "^1.4.2" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": ">=18" } }, - "node_modules/@nuxt/telemetry": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@nuxt/telemetry/-/telemetry-2.5.3.tgz", - "integrity": "sha512-Ghv2MgWbJcUM9G5Dy3oQP0cJkUwEgaiuQxEF61FXJdn0a69Q4StZEP/hLF0MWPM9m6EvAwI7orxkJHM7MrmtVg==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@nuxt/kit": "^3.8.2", - "ci-info": "^4.0.0", - "consola": "^3.2.3", - "create-require": "^1.1.1", - "defu": "^6.1.3", - "destr": "^2.0.2", - "dotenv": "^16.3.1", - "git-url-parse": "^13.1.1", - "is-docker": "^3.0.0", - "jiti": "^1.21.0", - "mri": "^1.2.0", - "nanoid": "^4.0.2", - "ofetch": "^1.3.3", - "parse-git-config": "^3.0.0", - "pathe": "^1.1.1", - "rc9": "^2.1.1", - "std-env": "^3.5.0" - }, - "bin": { - "nuxt-telemetry": "bin/nuxt-telemetry.mjs" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@nuxt/ui-templates": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@nuxt/ui-templates/-/ui-templates-1.3.1.tgz", - "integrity": "sha512-5gc02Pu1HycOVUWJ8aYsWeeXcSTPe8iX8+KIrhyEtEoOSkY0eMBuo0ssljB8wALuEmepv31DlYe5gpiRwkjESA==", - "dev": true + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@nuxt/vite-builder": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.10.3.tgz", - "integrity": "sha512-BqkbrYkEk1AVUJleofbqTRV+ltf2p1CDjGDK78zENPCgrSABlj4F4oK8rze8vmRY5qoH7kMZxgMa2dXVXCp6OA==", + "node_modules/@nuxt/vite-builder/node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@nuxt/kit": "3.10.3", - "@rollup/plugin-replace": "^5.0.5", - "@vitejs/plugin-vue": "^5.0.4", - "@vitejs/plugin-vue-jsx": "^3.1.0", - "autoprefixer": "^10.4.17", - "clear": "^0.1.0", - "consola": "^3.2.3", - "cssnano": "^6.0.3", - "defu": "^6.1.4", - "esbuild": "^0.20.1", - "escape-string-regexp": "^5.0.0", - "estree-walker": "^3.0.3", - "externality": "^1.0.2", - "fs-extra": "^11.2.0", - "get-port-please": "^3.1.2", - "h3": "^1.10.2", - "knitwork": "^1.0.0", - "magic-string": "^0.30.7", - "mlly": "^1.6.0", - "ohash": "^1.1.3", - "pathe": "^1.1.2", - "perfect-debounce": "^1.0.0", - "pkg-types": "^1.0.3", - "postcss": "^8.4.35", - "rollup-plugin-visualizer": "^5.12.0", - "std-env": "^3.7.0", - "strip-literal": "^2.0.0", - "ufo": "^1.4.0", - "unenv": "^1.9.0", - "unplugin": "^1.7.1", - "vite": "^5.1.4", - "vite-node": "^1.3.1", - "vite-plugin-checker": "^0.6.4", - "vue-bundle-renderer": "^2.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@nuxt/vite-builder/node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": ">=18" }, - "peerDependencies": { - "vue": "^3.3.4" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" } }, "node_modules/@nuxtjs/tailwindcss": { @@ -2231,9 +2340,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.24", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", - "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, "node_modules/@rollup/plugin-alias": { @@ -2269,9 +2378,9 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "25.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz", - "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==", + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", + "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -2373,9 +2482,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz", - "integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", + "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -2444,9 +2553,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", - "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", "cpu": [ "arm" ], @@ -2457,9 +2566,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz", - "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", "cpu": [ "arm64" ], @@ -2470,9 +2579,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz", - "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", "cpu": [ "arm64" ], @@ -2483,9 +2592,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz", - "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", "cpu": [ "x64" ], @@ -2496,9 +2605,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz", - "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", "cpu": [ "arm" ], @@ -2509,9 +2631,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz", - "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", "cpu": [ "arm64" ], @@ -2522,9 +2644,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz", - "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", "cpu": [ "arm64" ], @@ -2535,11 +2657,11 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz", - "integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", "cpu": [ - "ppc64le" + "ppc64" ], "dev": true, "optional": true, @@ -2548,9 +2670,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz", - "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", "cpu": [ "riscv64" ], @@ -2561,9 +2683,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz", - "integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", "cpu": [ "s390x" ], @@ -2574,9 +2696,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz", - "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", "cpu": [ "x64" ], @@ -2587,9 +2709,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz", - "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", "cpu": [ "x64" ], @@ -2600,9 +2722,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz", - "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", "cpu": [ "arm64" ], @@ -2613,9 +2735,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz", - "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", "cpu": [ "ia32" ], @@ -2626,9 +2748,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz", - "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", "cpu": [ "x64" ], @@ -2638,78 +2760,6 @@ "win32" ] }, - "node_modules/@sigstore/bundle": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.2.0.tgz", - "integrity": "sha512-5VI58qgNs76RDrwXNhpmyN/jKpq9evV/7f1XrcqcAfvxDl5SeVY/I5Rmfe96ULAV7/FK5dge9RBKGBJPhL1WsQ==", - "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.0.0.tgz", - "integrity": "sha512-dW2qjbWLRKGu6MIDUTBuJwXCnR8zivcSpf5inUzk7y84zqy/dji0/uahppoIgMoKeR+6pUZucrwHfkQQtiG9Rw==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.0.tgz", - "integrity": "sha512-zxiQ66JFOjVvP9hbhGj/F/qNdsZfkGb/dVXSanNRNuAzMlr4MC95voPUBX8//ZNnmv3uSYzdfR/JSkrgvZTGxA==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/sign": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.2.3.tgz", - "integrity": "sha512-LqlA+ffyN02yC7RKszCdMTS6bldZnIodiox+IkT8B2f8oRYXCB3LQ9roXeiEL21m64CVH1wyveYAORfD65WoSw==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^2.2.0", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.0", - "make-fetch-happen": "^13.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/tuf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.1.tgz", - "integrity": "sha512-9Iv40z652td/QbV0o5n/x25H9w6IYRt2pIGbTX55yFDYlApDQn/6YZomjz6+KBx69rXHLzHcbtTS586mDdFD+Q==", - "dev": true, - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.0", - "tuf-js": "^2.2.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@sigstore/verify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.1.0.tgz", - "integrity": "sha512-1fTqnqyTBWvV7cftUUFtDcHPdSox0N3Ub7C0lRyReYx4zZUlNTZjCV+HPy4Lre+r45dV7Qx5JLKvqqsgxuyYfg==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^2.2.0", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", @@ -2757,43 +2807,6 @@ "node": ">=10.13.0" } }, - "node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", - "dev": true, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", - "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", - "dev": true, - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.3" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -2801,9 +2814,9 @@ "dev": true }, "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "version": "1.17.15", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.15.tgz", + "integrity": "sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -2825,22 +2838,22 @@ "dev": true }, "node_modules/@unhead/dom": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.8.10.tgz", - "integrity": "sha512-dBeDbHrBjeU+eVgMsD91TGEazb1dwLrY0x/ve01CldMCmm+WcRu++SUW7s1QX84mzGH2EgFz78o1OPn6jpV3zw==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.9.16.tgz", + "integrity": "sha512-aZIAnnc89Csi1vV4mtlHYI765B7m1yuaXUuQiYHwr6glE9FLyy2X87CzEci4yPH/YbkKm0bGQRfcxXq6Eq0W7g==", "dev": true, "dependencies": { - "@unhead/schema": "1.8.10", - "@unhead/shared": "1.8.10" + "@unhead/schema": "1.9.16", + "@unhead/shared": "1.9.16" }, "funding": { "url": "https://github.com/sponsors/harlan-zw" } }, "node_modules/@unhead/schema": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.8.10.tgz", - "integrity": "sha512-cy8RGOPkwOVY5EmRoCgGV8AqLjy/226xBVTY54kBct02Om3hBdpB9FZa9frM910pPUXMI8PNmFgABO23O7IdJA==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.9.16.tgz", + "integrity": "sha512-V2BshX+I6D2wN4ys5so8RQDUgsggsxW9FVBiuQi4h8oPWtHclogxzDiHa5BH2TgvNIoUxLnLYNAShMGipmVuUw==", "dev": true, "dependencies": { "hookable": "^5.5.3", @@ -2851,40 +2864,40 @@ } }, "node_modules/@unhead/shared": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.8.10.tgz", - "integrity": "sha512-pEFryAs3EmV+ShDQx2ZBwUnt5l3RrMrXSMZ50oFf+MImKZNARVvD4+3I8fEI9wZh+Zq0JYG3UAfzo51MUP+Juw==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.9.16.tgz", + "integrity": "sha512-pfJnArULCY+GBr7OtYyyxihRiQLkT31TpyK6nUKIwyax4oNOGyhNfk0RFzNq16BwLg60d1lrc5bd5mZGbfClMA==", "dev": true, "dependencies": { - "@unhead/schema": "1.8.10" + "@unhead/schema": "1.9.16" }, "funding": { "url": "https://github.com/sponsors/harlan-zw" } }, "node_modules/@unhead/ssr": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/@unhead/ssr/-/ssr-1.8.10.tgz", - "integrity": "sha512-7wKRKDd8c2NFmMyPetj8Ah5u2hXunDBZT5Y2DH83O16PiMxx4/uobGamTV1EfcqjTvOKJvAqkrYZNYSWss99NQ==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@unhead/ssr/-/ssr-1.9.16.tgz", + "integrity": "sha512-8R1qt4VAemX4Iun/l7DnUBJqmxA/KaUSc2+/hRYPJYOopXdCWkoaxC1K1ROX2vbRF7qmjdU5ik/a27kSPN94gg==", "dev": true, "dependencies": { - "@unhead/schema": "1.8.10", - "@unhead/shared": "1.8.10" + "@unhead/schema": "1.9.16", + "@unhead/shared": "1.9.16" }, "funding": { "url": "https://github.com/sponsors/harlan-zw" } }, "node_modules/@unhead/vue": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.8.10.tgz", - "integrity": "sha512-KF8pftHnxnlBlgNpKXWLTg3ZUtkuDCxRPUFSDBy9CtqRSX/qvAhLZ26mbqRVmHj8KigiRHP/wnPWNyGnUx20Bg==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.9.16.tgz", + "integrity": "sha512-kpMWWwm8cOwo4gw4An43pz30l2CqNtmJpX5Xsu79rwf6Viq8jHAjk6BGqyKy220M2bpa0Va4fnR532SgGO1YgQ==", "dev": true, "dependencies": { - "@unhead/schema": "1.8.10", - "@unhead/shared": "1.8.10", + "@unhead/schema": "1.9.16", + "@unhead/shared": "1.9.16", "hookable": "^5.5.3", - "unhead": "1.8.10" + "unhead": "1.9.16" }, "funding": { "url": "https://github.com/sponsors/harlan-zw" @@ -2894,9 +2907,9 @@ } }, "node_modules/@vercel/nft": { - "version": "0.26.4", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.26.4.tgz", - "integrity": "sha512-j4jCOOXke2t8cHZCIxu1dzKLHLcFmYzC3yqAK6MfZznOL1QIJKd0xcFsXK3zcqzU7ScsE2zWkiMMNHGMHgp+FA==", + "version": "0.26.5", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.26.5.tgz", + "integrity": "sha512-NHxohEqad6Ra/r4lGknO52uc/GrWILXAMs1BB4401GTqww0fw1bAqzpG1XHuDO+dprg4GvsD9ZLLSsdo78p9hQ==", "dev": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.5", @@ -2952,6 +2965,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -2981,9 +2995,9 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", - "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz", + "integrity": "sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" @@ -2994,35 +3008,35 @@ } }, "node_modules/@vitejs/plugin-vue-jsx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-3.1.0.tgz", - "integrity": "sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-4.0.0.tgz", + "integrity": "sha512-A+6wL2AdQhDsLsDnY+2v4rRDI1HLJGIMc97a8FURO9tqKsH5QvjWrzsa5DH3NlZsM742W2wODl2fF+bfcTWtXw==", "dev": true, "dependencies": { - "@babel/core": "^7.23.3", - "@babel/plugin-transform-typescript": "^7.23.3", - "@vue/babel-plugin-jsx": "^1.1.5" + "@babel/core": "^7.24.6", + "@babel/plugin-transform-typescript": "^7.24.6", + "@vue/babel-plugin-jsx": "^1.2.2" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^4.0.0 || ^5.0.0", + "vite": "^5.0.0", "vue": "^3.0.0" } }, "node_modules/@vue-macros/common": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.10.1.tgz", - "integrity": "sha512-uftSpfwdwitcQT2lM8aVxcfe5rKQBzC9jMrtJM5sG4hEuFyfIvnJihpPpnaWxY+X4p64k+YYXtBFv+1O5Bq3dg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.12.2.tgz", + "integrity": "sha512-+NGfhrPvPNOb3Wg9PNPEXPe0HTXmVe6XJawL1gi3cIjOSGIhpOdvmMT2cRuWb265IpA/PeL5Sqo0+DQnEDxLvw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", + "@babel/types": "^7.25.0", "@rollup/pluginutils": "^5.1.0", - "@vue/compiler-sfc": "^3.4.13", - "ast-kit": "^0.11.3", + "@vue/compiler-sfc": "^3.4.34", + "ast-kit": "^1.0.1", "local-pkg": "^0.5.0", - "magic-string-ast": "^0.3.0" + "magic-string-ast": "^0.6.2" }, "engines": { "node": ">=16.14.0" @@ -3037,25 +3051,25 @@ } }, "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.1.tgz", - "integrity": "sha512-jtEXim+pfyHWwvheYwUwSXm43KwQo8nhOBDyjrUITV6X2tB7lJm6n/+4sqR8137UVZZul5hBzWHdZ2uStYpyRQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.2.tgz", + "integrity": "sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw==", "dev": true }, "node_modules/@vue/babel-plugin-jsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.1.tgz", - "integrity": "sha512-Yy9qGktktXhB39QE99So/BO2Uwm/ZG+gpL9vMg51ijRRbINvgbuhyJEi4WYmGRMx/MSTfK0xjgZ3/MyY+iLCEg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.2.tgz", + "integrity": "sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-module-imports": "~7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", - "@vue/babel-helper-vue-transform-on": "1.2.1", - "@vue/babel-plugin-resolve-type": "1.2.1", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "@vue/babel-helper-vue-transform-on": "1.2.2", + "@vue/babel-plugin-resolve-type": "1.2.2", "camelcase": "^6.3.0", "html-tags": "^3.3.1", "svg-tags": "^1.0.0" @@ -3069,33 +3083,69 @@ } } }, + "node_modules/@vue/babel-plugin-jsx/node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@vue/babel-plugin-resolve-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.1.tgz", - "integrity": "sha512-IOtnI7pHunUzHS/y+EG/yPABIAp0VN8QhQ0UCS09jeMVxgAnI9qdOzO85RXdQGxq+aWCdv8/+k3W0aYO6j/8fQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.2.tgz", + "integrity": "sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A==", "dev": true, "dependencies": { "@babel/code-frame": "^7.23.5", - "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-module-imports": "~7.22.15", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/parser": "^7.23.6", + "@babel/parser": "^7.23.9", "@vue/compiler-sfc": "^3.4.15" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, + "node_modules/@vue/babel-plugin-resolve-type/node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@vue/compiler-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", - "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.36.tgz", + "integrity": "sha512-qBkndgpwFKdupmOPoiS10i7oFdN7a+4UNDlezD0GlQ1kuA1pNrscg9g12HnB5E8hrWSuEftRsbJhL1HI2zpJhg==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/shared": "3.4.21", - "entities": "^4.5.0", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.36", + "entities": "^5.0.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", + "integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/@vue/compiler-core/node_modules/estree-walker": { @@ -3105,30 +3155,30 @@ "dev": true }, "node_modules/@vue/compiler-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", - "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.36.tgz", + "integrity": "sha512-eEIjy4GwwZTFon/Y+WO8tRRNGqylaRlA79T1RLhUpkOzJ7EtZkkb8MurNfkqY6x6Qiu0R7ESspEF7GkPR/4yYg==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-core": "3.4.36", + "@vue/shared": "3.4.36" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", - "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.36.tgz", + "integrity": "sha512-rhuHu7qztt/rNH90dXPTzhB7hLQT2OC4s4GrPVqmzVgPY4XBlfWmcWzn4bIPEWNImt0CjO7kfHAf/1UXOtx3vw==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/compiler-core": "3.4.21", - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.36", + "@vue/compiler-dom": "3.4.36", + "@vue/compiler-ssr": "3.4.36", + "@vue/shared": "3.4.36", "estree-walker": "^2.0.2", - "magic-string": "^0.30.7", - "postcss": "^8.4.35", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.40", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { @@ -3138,68 +3188,125 @@ "dev": true }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", - "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.36.tgz", + "integrity": "sha512-Wt1zyheF0zVvRJyhY74uxQbnkXV2Le/JPOrAxooR4rFYKC7cFr+cRqW6RU3cM/bsTy7sdZ83IDuy/gLPSfPGng==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.36", + "@vue/shared": "3.4.36" } }, "node_modules/@vue/devtools-api": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz", - "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.3.tgz", + "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==", "dev": true }, + "node_modules/@vue/devtools-core": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.3.3.tgz", + "integrity": "sha512-i6Bwkx4OwfY0QVHjAdsivhlzZ2HMj7fbNRYJsWspQ+dkA1f3nTzycPqZmVUsm2TGkbQlhTMhCAdDoP97JKoc+g==", + "dev": true, + "dependencies": { + "@vue/devtools-kit": "^7.3.3", + "@vue/devtools-shared": "^7.3.3", + "mitt": "^3.0.1", + "nanoid": "^3.3.4", + "pathe": "^1.1.2", + "vite-hot-client": "^0.2.3" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.3.tgz", + "integrity": "sha512-m+dFI57BrzKYPKq73mt4CJ5GWld5OLBseLHPHGVP7CaILNY9o1gWVJWAJeF8XtQ9LTiMxZSaK6NcBsFuxAhD0g==", + "dev": true, + "dependencies": { + "@vue/devtools-shared": "^7.3.3", + "birpc": "^0.2.17", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.7.tgz", + "integrity": "sha512-M9EU1/bWi5GNS/+IZrAhwGOVZmUTN4MH22Hvh35nUZZg9AZP2R2OhfCb+MG4EtAsrUEYlu3R43/SIj3G7EZYtQ==", + "dev": true, + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/reactivity": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", - "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.36.tgz", + "integrity": "sha512-wN1aoCwSoqrt1yt8wO0gc13QaC+Vk1o6AoSt584YHNnz6TGDhh1NCMUYgAnvp4HEIkLdGsaC1bvu/P+wpoDEXw==", "dev": true, "dependencies": { - "@vue/shared": "3.4.21" + "@vue/shared": "3.4.36" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", - "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.36.tgz", + "integrity": "sha512-9+TR14LAVEerZWLOm/N/sG2DVYhrH2bKgFrbH/FVt/Q8Jdw4OtdcGMRC6Tx8VAo0DA1eqAqrZaX0fbOaOxxZ4A==", "dev": true, "dependencies": { - "@vue/reactivity": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/reactivity": "3.4.36", + "@vue/shared": "3.4.36" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", - "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.36.tgz", + "integrity": "sha512-2Qe2fKkLxgZBVvHrG0QMNLL4bsx7Ae88pyXebY2WnQYABpOnGYvA+axMbcF9QwM4yxnsv+aELbC0eiNVns7mGw==", "dev": true, "dependencies": { - "@vue/runtime-core": "3.4.21", - "@vue/shared": "3.4.21", + "@vue/reactivity": "3.4.36", + "@vue/runtime-core": "3.4.36", + "@vue/shared": "3.4.36", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", - "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.36.tgz", + "integrity": "sha512-2XW90Rq8+Y7S1EIsAuubZVLm0gCU8HYb5mRAruFdwfC3XSOU5/YKePz29csFzsch8hXaY5UHh7ZMddmi1XTJEA==", "dev": true, "dependencies": { - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-ssr": "3.4.36", + "@vue/shared": "3.4.36" }, "peerDependencies": { - "vue": "3.4.21" + "vue": "3.4.36" } }, "node_modules/@vue/shared": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.36.tgz", + "integrity": "sha512-fdPLStwl1sDfYuUftBaUVn2pIrVFDASYerZSrlBvVBfylObPA1gtcWJHy5Ox8jLEJ524zBibss488Q3SZtU1uA==", "dev": true }, "node_modules/abbrev": { @@ -3208,6 +3315,18 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3222,9 +3341,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3234,9 +3353,9 @@ } }, "node_modules/acorn-import-attributes": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.2.tgz", - "integrity": "sha512-O+nfJwNolEA771IYJaiLWK1UAwjNsQmZbTRqqwBYxCgVQTmpFEMvBw6LOIQV0Me339L5UMVYFyRohGnGlQDdIQ==", + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -3254,19 +3373,6 @@ "node": ">= 6.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -3350,53 +3456,131 @@ "dev": true }, "node_modules/archiver": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.2.tgz", - "integrity": "sha512-UQ/2nW7NMl1G+1UnrLypQw1VdT9XZg/ECcKPq7l+STzStrSivFIXIp34D8M5zeNGW5NoOupdYCHv6VySCPNNlw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^5.0.1" + "zip-stream": "^6.0.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/archiver-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", - "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "dependencies": { - "glob": "^8.0.0", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { - "delegates": "^1.0.0", + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/archiver-utils/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", "readable-stream": "^3.6.0" }, "engines": { "node": ">=10" } }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -3410,41 +3594,39 @@ "dev": true }, "node_modules/ast-kit": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.11.3.tgz", - "integrity": "sha512-qdwwKEhckRk0XE22/xDdmU3v/60E8Edu4qFhgTLIhGGDs/PAJwLw9pQn8Rj99PitlbBZbYpx0k/lbir4kg0SuA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.0.1.tgz", + "integrity": "sha512-XdXKlmX3YIrGKJS7d324CAbswH+C1klMCIRQ4VRy0+iPxGeP2scVOoYd09/V6uGjGAi/ZuEwBLzT7xBerSKNQg==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.5", - "@rollup/pluginutils": "^5.1.0", - "pathe": "^1.1.1" + "@babel/parser": "^7.24.8", + "pathe": "^1.1.2" }, "engines": { "node": ">=16.14.0" } }, "node_modules/ast-walker-scope": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.5.0.tgz", - "integrity": "sha512-NsyHMxBh4dmdEHjBo1/TBZvCKxffmZxRYhmclfu0PP6Aftre47jOHYaYaNqJcV0bxihxFXhDkzLHUwHc0ocd0Q==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.6.1.tgz", + "integrity": "sha512-0ZdQEsSfH3mX4BFbRCc3xOBjx5bDbm73+aAdQOHerPQNf8K0XFMAv79ucd2BpnSc4UMyvBDixiroT8yjm2Y6bw==", "dev": true, "dependencies": { - "@babel/parser": "^7.22.7", - "ast-kit": "^0.9.4" + "@babel/parser": "^7.24.0", + "ast-kit": "^0.12.1" }, "engines": { "node": ">=16.14.0" } }, "node_modules/ast-walker-scope/node_modules/ast-kit": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.9.5.tgz", - "integrity": "sha512-kbL7ERlqjXubdDd+szuwdlQ1xUxEz9mCz1+m07ftNVStgwRb2RWw+U6oKo08PAvOishMxiqz1mlJyLl8yQx2Qg==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.12.2.tgz", + "integrity": "sha512-es1zHFsnZ4Y4efz412nnrU3KvVAhgqy90a7Yt9Wpi5vQ3l4aYMOX0Qx4FD0elKr5ITEhiUGCSFcgGYf4YTuACg==", "dev": true, "dependencies": { - "@babel/parser": "^7.22.7", - "@rollup/pluginutils": "^5.0.2", - "pathe": "^1.1.1" + "@babel/parser": "^7.24.6", + "pathe": "^1.1.2" }, "engines": { "node": ">=16.14.0" @@ -3472,9 +3654,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.17", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", - "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -3491,11 +3673,11 @@ } ], "dependencies": { - "browserslist": "^4.22.2", - "caniuse-lite": "^1.0.30001578", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -3521,12 +3703,32 @@ "dev": true }, "node_modules/bare-events": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.1.tgz", - "integrity": "sha512-9GYPpsPFvrWBkelIhOhTWtkeZxVxZOdb3VnFTCzlOo3OjvmTvzLoZFUT8kNFACx0vJej6QPney1Cf9BvzCNE/A==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", "dev": true, "optional": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -3582,21 +3784,21 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -3613,10 +3815,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -3625,13 +3827,37 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, "engines": { - "node": "*" + "node": ">=8.0.0" } }, "node_modules/buffer-from": { @@ -3652,15 +3878,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "dependencies": { - "semver": "^7.0.0" - } - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -3677,23 +3894,31 @@ } }, "node_modules/c12": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/c12/-/c12-1.9.0.tgz", - "integrity": "sha512-7KTCZXdIbOA2hLRQ+1KzJ15Qp9Wn58one74dkihMVp2H6EzKTa3OYBy0BSfS1CCcmxYyqeX8L02m40zjQ+dstg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-1.11.1.tgz", + "integrity": "sha512-KDU0TvSvVdaYcQKQ6iPHATGz/7p/KiVjPg4vQrB6Jg/wX9R0yl5RZxWm9IoZqaIHD2+6PZd81+KMGwRr/lRIUg==", "dev": true, "dependencies": { - "chokidar": "^3.5.3", - "confbox": "^0.1.3", + "chokidar": "^3.6.0", + "confbox": "^0.1.7", "defu": "^6.1.4", - "dotenv": "^16.3.2", - "giget": "^1.2.1", - "jiti": "^1.21.0", - "mlly": "^1.5.0", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^1.21.6", + "mlly": "^1.7.1", "ohash": "^1.1.3", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", - "pkg-types": "^1.0.3", - "rc9": "^2.1.1" + "pkg-types": "^1.1.1", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.4" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } } }, "node_modules/cac": { @@ -3705,75 +3930,6 @@ "node": ">=8" } }, - "node_modules/cacache": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.2.tgz", - "integrity": "sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/cache-content-type": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", @@ -3830,9 +3986,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001591", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", - "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", + "version": "1.0.30001650", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001650.tgz", + "integrity": "sha512-fgEc7hP/LB7iicdXHUI9VsBsMZmUmlVJeQP2qqQW+3lkqVhbmjEU8zp+h5stWeilX+G7uXuIUIIlWlDw9jdt8g==", "dev": true, "funding": [ { @@ -3967,15 +4123,6 @@ "consola": "^3.2.3" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/clear": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", @@ -4152,19 +4299,38 @@ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true }, + "node_modules/compatx": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/compatx/-/compatx-0.1.8.tgz", + "integrity": "sha512-jcbsEAR81Bt5s1qOFymBufmCbXCXbk0Ql+K5ouj6gCyx2yHlu6AgmGIi9HxfKixpUDO5bCFJUHQ5uM6ecbTebw==", + "dev": true + }, "node_modules/compress-commons": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.3.tgz", - "integrity": "sha512-/UIcLWvwAQyVibgpQDPtfNM3SvqN7G9elAPAV7GM0L53EbNWwWiCsWtK8Fwed/APEbptPHXs5PuW+y8Bq8lFTA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "crc32-stream": "^5.0.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/concat-map": { @@ -4174,9 +4340,9 @@ "dev": true }, "node_modules/confbox": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.3.tgz", - "integrity": "sha512-eH3ZxAihl1PhKfpr4VfEN6/vUd87fmgb6JkldHgg/YR6aEBhW63qUDgzP2Y6WM0UumdsYp5H3kibalXAdHfbgg==", + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", "dev": true }, "node_modules/consola": { @@ -4242,9 +4408,9 @@ "dev": true }, "node_modules/cookie-es": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.0.0.tgz", - "integrity": "sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", "dev": true }, "node_modules/cookies": { @@ -4260,6 +4426,21 @@ "node": ">= 0.8" } }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -4279,16 +4460,16 @@ } }, "node_modules/crc32-stream": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.1.tgz", - "integrity": "sha512-lO1dFui+CEUh/ztYIpgpKItKW9Bb4NWakCRJrnqAbFIYD+OZAwb2VfD5T5eXMw2FNcsDHkQcNl/Wh3iVXYwU6g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "dependencies": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } }, "node_modules/create-require": { @@ -4298,14 +4479,23 @@ "dev": true }, "node_modules/croner": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/croner/-/croner-8.0.1.tgz", - "integrity": "sha512-Hq1+lXVgjJjcS/U+uk6+yVmtxami0r0b+xVtlGyABgdz110l/kOnHWvlSI7nVzrTl8GCdZHwZS4pbBFT7hSL/g==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/croner/-/croner-8.1.1.tgz", + "integrity": "sha512-1VdUuRnQP4drdFkS8NKvDR1NBgevm8TOuflcaZEKsxw42CxonjW/2vkj1AKlinJb4ZLwBcuWF9GiPr7FQc6AQA==", "dev": true, "engines": { "node": ">=18.0" } }, + "node_modules/cronstrue": { + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.50.0.tgz", + "integrity": "sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg==", + "dev": true, + "bin": { + "cronstrue": "bin/cli.js" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4350,9 +4540,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz", - "integrity": "sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -4421,16 +4611,16 @@ "dev": true }, "node_modules/cssnano": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.5.tgz", - "integrity": "sha512-tpTp/ukgrElwu3ESFY4IvWnGn8eTt8cJhC2aAbtA3lvUlxp6t6UPv8YCLjNnEGiFreT1O0LiOM1U3QyTBVFl2A==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.4.tgz", + "integrity": "sha512-rQgpZra72iFjiheNreXn77q1haS2GEy69zCMbu4cpXCFPMQF+D4Ik5V7ktMzUF/sA7xCIgcqHwGPnCD+0a1vHg==", "dev": true, "dependencies": { - "cssnano-preset-default": "^6.0.5", - "lilconfig": "^3.1.1" + "cssnano-preset-default": "^7.0.4", + "lilconfig": "^3.1.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "funding": { "type": "opencollective", @@ -4441,55 +4631,56 @@ } }, "node_modules/cssnano-preset-default": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.5.tgz", - "integrity": "sha512-M+qRDEr5QZrfNl0B2ySdbTLGyNb8kBcSjuwR7WBamYBOEREH9t2efnB/nblekqhdGLZdkf4oZNetykG2JWRdZQ==", - "dev": true, - "dependencies": { - "css-declaration-sorter": "^7.1.1", - "cssnano-utils": "^4.0.1", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.0.3", - "postcss-convert-values": "^6.0.4", - "postcss-discard-comments": "^6.0.1", - "postcss-discard-duplicates": "^6.0.2", - "postcss-discard-empty": "^6.0.2", - "postcss-discard-overridden": "^6.0.1", - "postcss-merge-longhand": "^6.0.3", - "postcss-merge-rules": "^6.0.4", - "postcss-minify-font-values": "^6.0.2", - "postcss-minify-gradients": "^6.0.2", - "postcss-minify-params": "^6.0.3", - "postcss-minify-selectors": "^6.0.2", - "postcss-normalize-charset": "^6.0.1", - "postcss-normalize-display-values": "^6.0.1", - "postcss-normalize-positions": "^6.0.1", - "postcss-normalize-repeat-style": "^6.0.1", - "postcss-normalize-string": "^6.0.1", - "postcss-normalize-timing-functions": "^6.0.1", - "postcss-normalize-unicode": "^6.0.3", - "postcss-normalize-url": "^6.0.1", - "postcss-normalize-whitespace": "^6.0.1", - "postcss-ordered-values": "^6.0.1", - "postcss-reduce-initial": "^6.0.3", - "postcss-reduce-transforms": "^6.0.1", - "postcss-svgo": "^6.0.2", - "postcss-unique-selectors": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.4.tgz", + "integrity": "sha512-jQ6zY9GAomQX7/YNLibMEsRZguqMUGuupXcEk2zZ+p3GUxwCAsobqPYE62VrJ9qZ0l9ltrv2rgjwZPBIFIjYtw==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^5.0.0", + "postcss-calc": "^10.0.0", + "postcss-colormin": "^7.0.1", + "postcss-convert-values": "^7.0.2", + "postcss-discard-comments": "^7.0.1", + "postcss-discard-duplicates": "^7.0.0", + "postcss-discard-empty": "^7.0.0", + "postcss-discard-overridden": "^7.0.0", + "postcss-merge-longhand": "^7.0.2", + "postcss-merge-rules": "^7.0.2", + "postcss-minify-font-values": "^7.0.0", + "postcss-minify-gradients": "^7.0.0", + "postcss-minify-params": "^7.0.1", + "postcss-minify-selectors": "^7.0.2", + "postcss-normalize-charset": "^7.0.0", + "postcss-normalize-display-values": "^7.0.0", + "postcss-normalize-positions": "^7.0.0", + "postcss-normalize-repeat-style": "^7.0.0", + "postcss-normalize-string": "^7.0.0", + "postcss-normalize-timing-functions": "^7.0.0", + "postcss-normalize-unicode": "^7.0.1", + "postcss-normalize-url": "^7.0.0", + "postcss-normalize-whitespace": "^7.0.0", + "postcss-ordered-values": "^7.0.1", + "postcss-reduce-initial": "^7.0.1", + "postcss-reduce-transforms": "^7.0.0", + "postcss-svgo": "^7.0.1", + "postcss-unique-selectors": "^7.0.1" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, "node_modules/cssnano-utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz", - "integrity": "sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz", + "integrity": "sha512-Uij0Xdxc24L6SirFr25MlwC2rCFX6scyUmuKpzI+JQ7cyqDEwD42fJ0xfB3yLfOnRDU5LKGgjQ9FA6LYh76GWQ==", "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -4535,9 +4726,9 @@ "dev": true }, "node_modules/db0": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/db0/-/db0-0.1.3.tgz", - "integrity": "sha512-g8mXmj+t5MRXiA1ARjhfLd6Acy98VLVd8VL5LOBZ49P4A7qzoGgt6pC/gDWuP4zS3BiqSnKXTjTQXviMsD/sxA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/db0/-/db0-0.1.4.tgz", + "integrity": "sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==", "dev": true, "peerDependencies": { "@libsql/client": "^0.5.2", @@ -4557,9 +4748,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -4672,18 +4863,18 @@ } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "dev": true, "engines": { "node": ">=8" } }, "node_modules/devalue": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", - "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", + "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==", "dev": true }, "node_modules/didyoumean": { @@ -4808,9 +4999,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.689", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.689.tgz", - "integrity": "sha512-GatzRKnGPS1go29ep25reM94xxd1Wj8ritU0yRhCJ/tr1Bg8gKnm6R9O/yPOhGQBoLMZ9ezfrpghNaTw97C/PQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.5.tgz", + "integrity": "sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==", "dev": true }, "node_modules/emoji-regex": { @@ -4828,20 +5019,10 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/enhanced-resolve": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.1.tgz", - "integrity": "sha512-3d3JRbwsCLJsYgvb6NuWEG44jjPSOMuS73L/6+7BZuoKm3W+qXnSoIYVHi8dG7Qcg4inAY4jbzkZ7MnskePeDg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -4863,34 +5044,25 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, "node_modules/error-stack-parser-es": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.1.tgz", - "integrity": "sha512-g/9rfnvnagiNf+DRMHEVGuGuIBlCIMDFoTA616HaP2l9PlCjGjVhD98PNbVSJvmK4TttqT5mV5tInMhoFgi+aA==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", + "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" } }, + "node_modules/errx": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/errx/-/errx-0.1.0.tgz", + "integrity": "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==", + "dev": true + }, "node_modules/esbuild": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", - "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -4900,29 +5072,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.1", - "@esbuild/android-arm": "0.20.1", - "@esbuild/android-arm64": "0.20.1", - "@esbuild/android-x64": "0.20.1", - "@esbuild/darwin-arm64": "0.20.1", - "@esbuild/darwin-x64": "0.20.1", - "@esbuild/freebsd-arm64": "0.20.1", - "@esbuild/freebsd-x64": "0.20.1", - "@esbuild/linux-arm": "0.20.1", - "@esbuild/linux-arm64": "0.20.1", - "@esbuild/linux-ia32": "0.20.1", - "@esbuild/linux-loong64": "0.20.1", - "@esbuild/linux-mips64el": "0.20.1", - "@esbuild/linux-ppc64": "0.20.1", - "@esbuild/linux-riscv64": "0.20.1", - "@esbuild/linux-s390x": "0.20.1", - "@esbuild/linux-x64": "0.20.1", - "@esbuild/netbsd-x64": "0.20.1", - "@esbuild/openbsd-x64": "0.20.1", - "@esbuild/sunos-x64": "0.20.1", - "@esbuild/win32-arm64": "0.20.1", - "@esbuild/win32-ia32": "0.20.1", - "@esbuild/win32-x64": "0.20.1" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escalade": { @@ -4970,14 +5142,32 @@ "node": ">= 0.6" } }, - "node_modules/execa": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", - "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", "human-signals": "^4.3.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", @@ -4993,12 +5183,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exponential-backoff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "dev": true - }, "node_modules/externality": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/externality/-/externality-1.0.2.tgz", @@ -5033,6 +5217,15 @@ "node": ">=8.6.0" } }, + "node_modules/fast-npm-meta": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/fast-npm-meta/-/fast-npm-meta-0.1.1.tgz", + "integrity": "sha512-uS9DjGncI/9XZ6HJFrci0WzSi++N8Jskbb2uB7+9SQlrgA3VaLhXhV9Gl5HwIGESHkayYYZFGnVNhJwRDKCWIA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -5049,9 +5242,9 @@ "dev": true }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -5060,15 +5253,6 @@ "node": ">=8" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", @@ -5139,18 +5323,6 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5184,6 +5356,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", "dev": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", @@ -5237,18 +5410,18 @@ } }, "node_modules/giget": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", - "integrity": "sha512-4VG22mopWtIeHwogGSy1FViXVo0YT+m6BrqZfz0JJFwbSsePsCdOzdLIIli5BtMp7Xe8f/o2OmBpQX2NBOC24g==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", "dev": true, "dependencies": { - "citty": "^0.1.5", + "citty": "^0.1.6", "consola": "^3.2.3", - "defu": "^6.1.3", - "node-fetch-native": "^1.6.1", - "nypm": "^0.3.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", "ohash": "^1.1.3", - "pathe": "^1.1.1", + "pathe": "^1.1.2", "tar": "^6.2.0" }, "bin": { @@ -5275,9 +5448,9 @@ } }, "node_modules/git-url-parse": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.1.tgz", - "integrity": "sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-14.1.0.tgz", + "integrity": "sha512-8xg65dTxGHST3+zGpycMMFZcoTzAdZ2dOtu4vmgIfkTFnVHBxHMzBC2L1k8To7EmrSiHesT8JgPLT91VKw1B5g==", "dev": true, "dependencies": { "git-up": "^7.0.0" @@ -5287,6 +5460,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -5339,9 +5513,9 @@ } }, "node_modules/globby": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", - "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", @@ -5380,19 +5554,19 @@ } }, "node_modules/h3": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.11.1.tgz", - "integrity": "sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.12.0.tgz", + "integrity": "sha512-Zi/CcNeWBXDrFNlV0hUBJQR9F7a96RjMeAZweW/ZWkR9fuXrMcvKnSA63f/zZ9l0GgQOZDVHGvXivNN9PWOwhA==", "dev": true, "dependencies": { - "cookie-es": "^1.0.0", - "crossws": "^0.2.2", + "cookie-es": "^1.1.0", + "crossws": "^0.2.4", "defu": "^6.1.4", "destr": "^2.0.3", - "iron-webcrypto": "^1.0.0", + "iron-webcrypto": "^1.1.1", "ohash": "^1.1.3", - "radix3": "^1.1.0", - "ufo": "^1.4.0", + "radix3": "^1.1.2", + "ufo": "^1.5.3", "uncrypto": "^0.1.3", "unenv": "^1.9.0" } @@ -5463,27 +5637,6 @@ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "dev": true }, - "node_modules/hosted-git-info": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", - "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", - "dev": true, - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -5562,12 +5715,6 @@ "node": ">= 0.6" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -5584,31 +5731,6 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/http-shutdown": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/http-shutdown/-/http-shutdown-1.2.2.tgz", @@ -5647,18 +5769,25 @@ "node": ">=14.18.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/ignore": { "version": "5.3.1", @@ -5669,33 +5798,6 @@ "node": ">= 4" } }, - "node_modules/ignore-walk": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz", - "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==", - "dev": true, - "dependencies": { - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/image-meta": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/image-meta/-/image-meta-0.2.0.tgz", @@ -5710,24 +5812,6 @@ "optional": true, "peer": true }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5754,9 +5838,9 @@ } }, "node_modules/ioredis": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", - "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", "dev": true, "dependencies": { "@ioredis/commands": "^1.1.1", @@ -5777,23 +5861,10 @@ "url": "https://opencollective.com/ioredis" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/iron-webcrypto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.0.0.tgz", - "integrity": "sha512-anOK1Mktt8U1Xi7fCM3RELTuYbnFikQY5VtrDj7kPgpejV7d43tWKhzgioO0zpkazLEL/j/iayRqnJhrGfqUsg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", "dev": true, "funding": { "url": "https://github.com/sponsors/brc-dd" @@ -5932,12 +6003,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -5965,15 +6030,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-primitive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz", - "integrity": "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -6004,6 +6060,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-wsl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", @@ -6065,9 +6133,9 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -6091,12 +6159,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "dev": true - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -6109,15 +6171,6 @@ "node": ">=4" } }, - "node_modules/json-parse-even-better-errors": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", - "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6130,12 +6183,6 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", - "dev": true - }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -6148,15 +6195,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -6188,9 +6226,9 @@ } }, "node_modules/knitwork": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.0.0.tgz", - "integrity": "sha512-dWl0Dbjm6Xm+kDxhPQJsCBTxrJzuGl0aP9rhr+TG8D3l+GL90N8O8lYUi7dTSAN2uuDqCtNgb6aEuQH5wsiV8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.1.0.tgz", + "integrity": "sha512-oHnmiBUVHz1V+URE77PNot2lv3QiYU2zQf1JjOVkMt3YDKGbu8NAFr+c4mcNOhdsGrB/VpVbRwPwhiXrPhxQbw==", "dev": true }, "node_modules/koa": { @@ -6357,9 +6395,9 @@ "dev": true }, "node_modules/launch-editor": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", - "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.1.tgz", + "integrity": "sha512-elBx2l/tp9z99X5H/qev8uyDywVh0VXAwEbjk8kJhnc5grOFkGh7aW6q55me9xnYbss261XtnUrysZ+XvGbhQA==", "dev": true, "dependencies": { "picocolors": "^1.0.0", @@ -6403,9 +6441,9 @@ } }, "node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true, "engines": { "node": ">=14" @@ -6506,38 +6544,35 @@ } }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/magic-string-ast": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-0.3.0.tgz", - "integrity": "sha512-0shqecEPgdFpnI3AP90epXyxZy9g6CRZ+SZ7BcqFwYmtFEnZ1jpevcV5HoyVnlDS9gCnc1UIg3Rsvp3Ci7r8OA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-0.6.2.tgz", + "integrity": "sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==", "dev": true, "dependencies": { - "magic-string": "^0.30.2" + "magic-string": "^0.30.10" }, "engines": { "node": ">=16.14.0" } }, "node_modules/magicast": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.3.tgz", - "integrity": "sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz", + "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==", "dev": true, "dependencies": { - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", - "source-map-js": "^1.0.2" + "@babel/parser": "^7.24.4", + "@babel/types": "^7.24.0", + "source-map-js": "^1.2.0" } }, "node_modules/make-dir": { @@ -6564,28 +6599,6 @@ "semver": "bin/semver.js" } }, - "node_modules/make-fetch-happen": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", - "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", - "dev": true, - "dependencies": { - "@npmcli/agent": "^2.0.0", - "cacache": "^18.0.0", - "http-cache-semantics": "^4.1.1", - "is-lambda": "^1.0.1", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -6639,9 +6652,9 @@ } }, "node_modules/mime": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.1.tgz", - "integrity": "sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", "dev": true, "funding": [ "https://github.com/sponsors/broofa" @@ -6708,56 +6721,28 @@ } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-fetch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", - "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", - "dev": true, - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "dependencies": { - "minipass": "^3.0.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" }, "engines": { "node": ">= 8" } }, - "node_modules/minipass-flush/node_modules/minipass": { + "node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", @@ -6769,153 +6754,40 @@ "node": ">=8" } }, - "node_modules/minipass-flush/node_modules/yallist": { + "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", "dev": true, "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-json-stream/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mlly": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz", - "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==", - "dev": true, - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.0.3", - "ufo": "^1.3.2" + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" } }, "node_modules/mri": { @@ -6954,9 +6826,9 @@ } }, "node_modules/nanoid": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", - "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", + "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", "dev": true, "funding": [ { @@ -6968,7 +6840,7 @@ "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^14 || ^16 || >=18" + "node": "^18 || >=20" } }, "node_modules/negotiator": { @@ -6981,77 +6853,77 @@ } }, "node_modules/nitropack": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/nitropack/-/nitropack-2.9.1.tgz", - "integrity": "sha512-qgz2VKJoiFvEtC6JnFlaqVx5hirD/oonb6SIM6kQODeDjEHVaiOViZxg7pnAGq13Zu2vNHhaJe6Sf49AEG3J+A==", + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/nitropack/-/nitropack-2.9.7.tgz", + "integrity": "sha512-aKXvtNrWkOCMsQbsk4A0qQdBjrJ1ZcvwlTQevI/LAgLWLYc5L7Q/YiYxGLal4ITyNSlzir1Cm1D2ZxnYhmpMEw==", "dev": true, "dependencies": { - "@cloudflare/kv-asset-handler": "^0.3.1", - "@netlify/functions": "^2.6.0", + "@cloudflare/kv-asset-handler": "^0.3.4", + "@netlify/functions": "^2.8.0", "@rollup/plugin-alias": "^5.1.0", - "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-commonjs": "^25.0.8", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-replace": "^5.0.7", "@rollup/plugin-terser": "^0.4.4", "@rollup/pluginutils": "^5.1.0", "@types/http-proxy": "^1.17.14", - "@vercel/nft": "^0.26.4", - "archiver": "^6.0.1", - "c12": "^1.9.0", + "@vercel/nft": "^0.26.5", + "archiver": "^7.0.1", + "c12": "^1.11.1", "chalk": "^5.3.0", "chokidar": "^3.6.0", "citty": "^0.1.6", "consola": "^3.2.3", - "cookie-es": "^1.0.0", - "croner": "^8.0.1", + "cookie-es": "^1.1.0", + "croner": "^8.0.2", "crossws": "^0.2.4", - "db0": "^0.1.2", + "db0": "^0.1.4", "defu": "^6.1.4", "destr": "^2.0.3", "dot-prop": "^8.0.2", - "esbuild": "^0.20.1", + "esbuild": "^0.20.2", "escape-string-regexp": "^5.0.0", "etag": "^1.8.1", "fs-extra": "^11.2.0", "globby": "^14.0.1", "gzip-size": "^7.0.0", - "h3": "^1.11.1", + "h3": "^1.12.0", "hookable": "^5.5.3", "httpxy": "^0.1.5", - "is-primitive": "^3.0.1", - "jiti": "^1.21.0", + "ioredis": "^5.4.1", + "jiti": "^1.21.6", "klona": "^2.0.6", - "knitwork": "^1.0.0", + "knitwork": "^1.1.0", "listhen": "^1.7.2", - "magic-string": "^0.30.7", - "mime": "^4.0.1", - "mlly": "^1.6.1", + "magic-string": "^0.30.10", + "mime": "^4.0.3", + "mlly": "^1.7.1", "mri": "^1.2.0", - "node-fetch-native": "^1.6.2", - "ofetch": "^1.3.3", + "node-fetch-native": "^1.6.4", + "ofetch": "^1.3.4", "ohash": "^1.1.3", - "openapi-typescript": "^6.7.4", + "openapi-typescript": "^6.7.6", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", - "pkg-types": "^1.0.3", + "pkg-types": "^1.1.1", "pretty-bytes": "^6.1.1", - "radix3": "^1.1.0", - "rollup": "^4.12.0", + "radix3": "^1.1.2", + "rollup": "^4.18.0", "rollup-plugin-visualizer": "^5.12.0", "scule": "^1.3.0", - "semver": "^7.6.0", - "serve-placeholder": "^2.0.1", + "semver": "^7.6.2", + "serve-placeholder": "^2.0.2", "serve-static": "^1.15.0", "std-env": "^3.7.0", - "ufo": "^1.4.0", + "ufo": "^1.5.3", "uncrypto": "^0.1.3", "unctx": "^2.3.1", "unenv": "^1.9.0", - "unimport": "^3.7.1", - "unstorage": "^1.10.1", - "unwasm": "^0.3.7" + "unimport": "^3.7.2", + "unstorage": "^1.10.2", + "unwasm": "^0.3.9" }, "bin": { "nitro": "dist/cli/index.mjs", @@ -7082,13 +6954,10 @@ } }, "node_modules/node-addon-api": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", - "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", - "dev": true, - "engines": { - "node": "^16 || ^18 || >= 20" - } + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true }, "node_modules/node-fetch": { "version": "2.7.0", @@ -7111,9 +6980,9 @@ } }, "node_modules/node-fetch-native": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.2.tgz", - "integrity": "sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", "dev": true }, "node_modules/node-forge": { @@ -7125,34 +6994,10 @@ "node": ">= 6.13.0" } }, - "node_modules/node-gyp": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.0.1.tgz", - "integrity": "sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^13.0.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^4.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/node-gyp-build": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", - "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", "dev": true, "bin": { "node-gyp-build": "bin.js", @@ -7160,95 +7005,10 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/abbrev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/node-gyp/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/node-gyp/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-gyp/node_modules/nopt": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", - "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", - "dev": true, - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/node-gyp/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "dev": true, - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/nopt": { @@ -7266,21 +7026,6 @@ "node": ">=6" } }, - "node_modules/normalize-package-data": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", - "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", - "dev": true, - "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7299,154 +7044,62 @@ "node": ">=0.10.0" } }, - "node_modules/npm-bundled": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", - "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "path-key": "^4.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", - "dev": true, - "dependencies": { - "semver": "^7.1.1" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-package-arg": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", - "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", "dev": true, "dependencies": { - "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" } }, - "node_modules/npm-packlist": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, "dependencies": { - "ignore-walk": "^6.0.4" + "boolbase": "^1.0.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-pick-manifest": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", - "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", - "dev": true, - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^11.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.1.0.tgz", - "integrity": "sha512-PQCELXKt8Azvxnt5Y85GseQDJJlglTFM9L9U9gkv2y4e9s0k3GVDdOx3YoB6gm2Do0hlkzC39iCGXby+Wve1Bw==", - "dev": true, - "dependencies": { - "make-fetch-happen": "^13.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^11.0.0", - "proc-log": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, "node_modules/nuxi": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/nuxi/-/nuxi-3.10.1.tgz", - "integrity": "sha512-ZNt858+FOZDIiKKFJkXO7uJAnALytDdn1XbLgtZAqbtWNMayHbOnWcnxh+WSOE4H9uOi2+loWXEqKElmNWLgcQ==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/nuxi/-/nuxi-3.12.0.tgz", + "integrity": "sha512-6vRdiXTw9SajEQOUi6Ze/XaIXzy1q/sD5UqHQSv3yqTu7Pot5S7fEihNXV8LpcgLz+9HzjVt70r7jYe7R99c2w==", "dev": true, "bin": { "nuxi": "bin/nuxi.mjs", @@ -7455,73 +7108,77 @@ "nuxt-cli": "bin/nuxi.mjs" }, "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": "^16.10.0 || >=18.0.0" }, "optionalDependencies": { "fsevents": "~2.3.3" } }, "node_modules/nuxt": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.10.3.tgz", - "integrity": "sha512-NchGNiiz9g/ErJAb462W/lpX2NqcXYb9hugySKWvLXNdrjeAPiJ2/7mhgwUSiZA9MpjuQg3saiEajr1zlRIOCg==", + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.12.4.tgz", + "integrity": "sha512-/ddvyc2kgYYIN2UEjP8QIz48O/W3L0lZm7wChIDbOCj0vF/yLLeZHBaTb3aNvS9Hwp269nfjrm8j/mVxQK4RhA==", "dev": true, "dependencies": { "@nuxt/devalue": "^2.0.2", - "@nuxt/devtools": "^1.0.8", - "@nuxt/kit": "3.10.3", - "@nuxt/schema": "3.10.3", - "@nuxt/telemetry": "^2.5.3", - "@nuxt/ui-templates": "^1.3.1", - "@nuxt/vite-builder": "3.10.3", - "@unhead/dom": "^1.8.10", - "@unhead/ssr": "^1.8.10", - "@unhead/vue": "^1.8.10", - "@vue/shared": "^3.4.19", - "acorn": "8.11.3", - "c12": "^1.9.0", + "@nuxt/devtools": "^1.3.9", + "@nuxt/kit": "3.12.4", + "@nuxt/schema": "3.12.4", + "@nuxt/telemetry": "^2.5.4", + "@nuxt/vite-builder": "3.12.4", + "@unhead/dom": "^1.9.16", + "@unhead/ssr": "^1.9.16", + "@unhead/vue": "^1.9.16", + "@vue/shared": "^3.4.32", + "acorn": "8.12.1", + "c12": "^1.11.1", "chokidar": "^3.6.0", - "cookie-es": "^1.0.0", + "compatx": "^0.1.8", + "consola": "^3.2.3", + "cookie-es": "^1.1.0", "defu": "^6.1.4", "destr": "^2.0.3", - "devalue": "^4.3.2", - "esbuild": "^0.20.1", + "devalue": "^5.0.0", + "errx": "^0.1.0", + "esbuild": "^0.23.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", - "fs-extra": "^11.2.0", - "globby": "^14.0.1", - "h3": "^1.10.2", + "globby": "^14.0.2", + "h3": "^1.12.0", "hookable": "^5.5.3", - "jiti": "^1.21.0", + "ignore": "^5.3.1", + "jiti": "^1.21.6", "klona": "^2.0.6", - "knitwork": "^1.0.0", - "magic-string": "^0.30.7", - "mlly": "^1.6.0", - "nitropack": "^2.8.1", - "nuxi": "^3.10.1", - "nypm": "^0.3.6", - "ofetch": "^1.3.3", + "knitwork": "^1.1.0", + "magic-string": "^0.30.10", + "mlly": "^1.7.1", + "nitropack": "^2.9.7", + "nuxi": "^3.12.0", + "nypm": "^0.3.9", + "ofetch": "^1.3.4", "ohash": "^1.1.3", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", - "pkg-types": "^1.0.3", - "radix3": "^1.1.0", + "pkg-types": "^1.1.3", + "radix3": "^1.1.2", "scule": "^1.3.0", + "semver": "^7.6.3", "std-env": "^3.7.0", - "strip-literal": "^2.0.0", - "ufo": "^1.4.0", + "strip-literal": "^2.1.0", + "ufo": "^1.5.4", "ultrahtml": "^1.5.3", "uncrypto": "^0.1.3", "unctx": "^2.3.1", - "unenv": "^1.9.0", - "unimport": "^3.7.1", - "unplugin": "^1.7.1", - "unplugin-vue-router": "^0.7.0", + "unenv": "^1.10.0", + "unimport": "^3.9.0", + "unplugin": "^1.11.0", + "unplugin-vue-router": "^0.10.0", + "unstorage": "^1.10.2", "untyped": "^1.4.2", - "vue": "^3.4.19", - "vue-bundle-renderer": "^2.0.0", + "vue": "^3.4.32", + "vue-bundle-renderer": "^2.1.0", "vue-devtools-stub": "^0.1.0", - "vue-router": "^4.3.0" + "vue-router": "^4.4.0" }, "bin": { "nuxi": "bin/nuxt.mjs", @@ -7570,1115 +7227,1487 @@ "xss": "^1.0.14" } }, - "node_modules/nypm": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.6.tgz", - "integrity": "sha512-2CATJh3pd6CyNfU5VZM7qSwFu0ieyabkEdnogE30Obn1czrmOYiZ8DOZLe1yBdLKWoyD3Mcy2maUs+0MR3yVjQ==", + "node_modules/nuxt/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "citty": "^0.1.5", - "execa": "^8.0.1", - "pathe": "^1.1.2", - "ufo": "^1.3.2" - }, - "bin": { - "nypm": "dist/cli.mjs" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^14.16.0 || >=16.10.0" + "node": ">=18" } }, - "node_modules/nypm/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/nuxt/node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=18" } }, - "node_modules/nypm/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/nuxt/node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/nypm/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "node_modules/nuxt/node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=16.17.0" + "node": ">=18" } }, - "node_modules/nypm/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/nuxt/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=18" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/nuxt/node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "node_modules/nuxt/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], "dev": true, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/ofetch": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.3.3.tgz", - "integrity": "sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg==", + "node_modules/nuxt/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "destr": "^2.0.1", - "node-fetch-native": "^1.4.0", - "ufo": "^1.3.0" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/ohash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", - "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/nuxt/node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": ">=18" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/nuxt/node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "wrappy": "1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/nuxt/node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", - "dev": true - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "node_modules/nuxt/node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/open/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "node_modules/nuxt/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], "dev": true, - "bin": { - "is-docker": "cli.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/open/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/nuxt/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/openapi-typescript": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.4.tgz", - "integrity": "sha512-EZyeW9Wy7UDCKv0iYmKrq2pVZtquXiD/YHiUClAKqiMi42nodx/EQH11K6fLqjt1IZlJmVokrAsExsBMM2RROQ==", + "node_modules/nuxt/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "ansi-colors": "^4.1.3", - "fast-glob": "^3.3.2", - "js-yaml": "^4.1.0", - "supports-color": "^9.4.0", - "undici": "^5.28.2", - "yargs-parser": "^21.1.1" - }, - "bin": { - "openapi-typescript": "bin/cli.js" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/openapi-typescript/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "node_modules/nuxt/node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=18" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/nuxt/node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/pacote": { - "version": "17.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.6.tgz", - "integrity": "sha512-cJKrW21VRE8vVTRskJo78c/RCvwJCn1f4qgfxL4w77SOWrTCRcmfkYHlHtS0gqpgjv3zhXflRtgsrUCX5xwNnQ==", - "dev": true, - "dependencies": { - "@npmcli/git": "^5.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", - "cacache": "^18.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^11.0.0", - "npm-packlist": "^8.0.0", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^2.2.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" - }, + "node_modules/nuxt/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/parent-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", - "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "node_modules/nuxt/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "callsites": "^3.1.0" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/parse-git-config": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-3.0.0.tgz", - "integrity": "sha512-wXoQGL1D+2COYWCD35/xbiKma1Z15xvZL8cI25wvxzled58V51SJM04Urt/uznS900iQor7QO04SgdfT/XlbuA==", + "node_modules/nuxt/node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "git-config-path": "^2.0.0", - "ini": "^1.3.5" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/parse-git-config/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/parse-path": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", - "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "node_modules/nuxt/node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "protocols": "^2.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/parse-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", - "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "node_modules/nuxt/node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "parse-path": "^7.0.0" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "node_modules/nuxt/node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "node_modules/nuxt/node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", "dev": true, - "dependencies": { - "domhandler": "^5.0.2", - "parse5": "^7.0.0" + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/nypm": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz", + "integrity": "sha512-BI2SdqqTHg2d4wJh8P9A1W+bslg33vOE9IZDY6eR2QC+Pu1iNBVZUqczrd43rJb+fMzHU7ltAYKsEFY/kHMFcw==", "dev": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.16.0 || >=16.10.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "node_modules/nypm/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16.17" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "node_modules/nypm/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "engines": { - "node": "14 || >=16.14" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true - }, - "node_modules/path-type": { + "node_modules/nypm/node_modules/human-signals": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16.17.0" } }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/nypm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "engines": { - "node": ">=8.6" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "dev": true, "engines": { "node": ">= 6" } }, - "node_modules/pkg-types": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", - "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "node_modules/ofetch": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.3.4.tgz", + "integrity": "sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw==", "dev": true, "dependencies": { - "jsonc-parser": "^3.2.0", - "mlly": "^1.2.0", - "pathe": "^1.1.0" + "destr": "^2.0.3", + "node-fetch-native": "^1.6.3", + "ufo": "^1.5.3" } }, - "node_modules/portfinder": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", - "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "node_modules/ohash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", + "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "dependencies": { - "async": "^2.6.4", - "debug": "^3.2.7", - "mkdirp": "^0.5.6" + "ee-first": "1.1.1" }, "engines": { - "node": ">= 0.12.0" + "node": ">= 0.8" } }, - "node_modules/portfinder/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "dependencies": { - "lodash": "^4.17.14" + "wrappy": "1" } }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", + "dev": true + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "node_modules/open/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=8" }, - "peerDependencies": { - "postcss": "^8.2.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-colormin": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.3.tgz", - "integrity": "sha512-ECpkS+UZRyAtu/kjive2/1mihP+GNtgC8kcdU8ueWZi1ZVxMNnRziCLdhrWECJhEtSWijfX2Cl9XTTCK/hjGaA==", + "node_modules/open/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" + "is-docker": "^2.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=8" } }, - "node_modules/postcss-convert-values": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.4.tgz", - "integrity": "sha512-YT2yrGzPXoQD3YeA2kBo/696qNwn7vI+15AOS2puXWEvSWqdCqlOyDWRy5GNnOc9ACRGOkuQ4ESQEqPJBWt/GA==", + "node_modules/openapi-typescript": { + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.6.tgz", + "integrity": "sha512-c/hfooPx+RBIOPM09GSxABOZhYPblDoyaGhqBkD/59vtpN21jEuWKDlM0KYTvqJVlSYjKs0tBcIdeXKChlSPtw==", "dev": true, "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" + "ansi-colors": "^4.1.3", + "fast-glob": "^3.3.2", + "js-yaml": "^4.1.0", + "supports-color": "^9.4.0", + "undici": "^5.28.4", + "yargs-parser": "^21.1.1" }, + "bin": { + "openapi-typescript": "bin/cli.js" + } + }, + "node_modules/openapi-typescript/node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=12" }, - "peerDependencies": { - "postcss": "^8.4.31" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/postcss-custom-properties": { - "version": "13.3.5", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.5.tgz", - "integrity": "sha512-xHg8DTCMfN2nrqs2CQTF+0m5jgnzKL5zrW5Y05KF6xBRO0uDPxiplBm/xcr1o49SLbyJXkMuaRJKhRzkrquKnQ==", + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.8", - "@csstools/css-parser-algorithms": "^2.6.0", - "@csstools/css-tokenizer": "^2.2.3", - "@csstools/utilities": "^1.0.0", - "postcss-value-parser": "^4.2.0" + "callsites": "^3.1.0" }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": ">=8" } }, - "node_modules/postcss-discard-comments": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz", - "integrity": "sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==", + "node_modules/parse-git-config": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-3.0.0.tgz", + "integrity": "sha512-wXoQGL1D+2COYWCD35/xbiKma1Z15xvZL8cI25wvxzled58V51SJM04Urt/uznS900iQor7QO04SgdfT/XlbuA==", "dev": true, - "engines": { - "node": "^14 || ^16 || >=18.0" + "dependencies": { + "git-config-path": "^2.0.0", + "ini": "^1.3.5" }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.2.tgz", - "integrity": "sha512-U2rsj4w6pAGROCCcD13LP2eBIi1whUsXs4kgE6xkIuGfkbxCBSKhkCTWyowFd66WdVlLv0uM1euJKIgmdmZObg==", - "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=8" } }, - "node_modules/postcss-discard-empty": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.2.tgz", - "integrity": "sha512-rj6pVC2dVCJrP0Y2RkYTQEbYaCf4HEm+R/2StQgJqGHxAa3+KcYslNQhcRqjLHtl/4wpzipJluaJLqBj6d5eDQ==", - "dev": true, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } + "node_modules/parse-git-config/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz", - "integrity": "sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==", + "node_modules/parse-path": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", "dev": true, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "dependencies": { + "protocols": "^2.0.0" } }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "node_modules/parse-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" + "parse-path": "^7.0.0" } }, - "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "dev": true, "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" + "entities": "^4.4.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": "^8.4.21" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" - }, - "engines": { - "node": ">= 14" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" + "domhandler": "^5.0.2", + "parse5": "^7.0.0" }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/postcss-merge-longhand": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.3.tgz", - "integrity": "sha512-kF/y3DU8CRt+SX3tP/aG+2gkZI2Z7OXDsPU7FgxIJmuyhQQ1EHceIYcsp/alvzCm2P4c37Sfdu8nNrHc+YeyLg==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.0.3" - }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">= 0.8" } }, - "node_modules/postcss-merge-rules": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.4.tgz", - "integrity": "sha512-97iF3UJ5v8N1BWy38y+0l+Z8o5/9uGlEgtWic2PJPzoRrLB6Gxg8TVG93O0EK52jcLeMsywre26AUlX1YAYeHA==", + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.1", - "postcss-selector-parser": "^6.0.15" - }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=0.10.0" } }, - "node_modules/postcss-minify-font-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.2.tgz", - "integrity": "sha512-IedzbVMoX0a7VZWjSYr5qJ6C37rws8kl8diPBeMZLJfWKkgXuMFY5R/OxPegn/q9tK9ztd0XRH3aR0u2t+A7uQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": ">=8" } }, - "node_modules/postcss-minify-gradients": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.2.tgz", - "integrity": "sha512-vP5mF7iI6/5fcpv+rSfwWQekOE+8I1i7/7RjZPGuIjj6eUaZVeG4XZYZrroFuw1WQd51u2V32wyQFZ+oYdE7CA==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.1", - "postcss-value-parser": "^4.2.0" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=16 || 14 >=14.18" }, - "peerDependencies": { - "postcss": "^8.4.31" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/postcss-minify-params": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.3.tgz", - "integrity": "sha512-j4S74d3AAeCK5eGdQndXSrkxusV2ekOxbXGnlnZthMyZBBvSDiU34CihTASbJxuVB3bugudmwolS7+Dgs5OyOQ==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "dev": true, - "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.1", - "postcss-value-parser": "^4.2.0" - }, "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" + "node": "14 || >=16.14" } }, - "node_modules/postcss-minify-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz", - "integrity": "sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==", + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true + }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.15" - }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=12" }, - "peerDependencies": { - "postcss": "^8.4.31" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.11" - }, "engines": { - "node": ">=12.0" + "node": ">=8.6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz", + "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" }, - "peerDependencies": { - "postcss": "^8.2.14" + "engines": { + "node": ">= 0.12.0" } }, - "node_modules/postcss-nesting": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.4.tgz", - "integrity": "sha512-WuCe0KnP4vKjLZK8VNoUWKL8ZLOv/5jiM94mHcI3VszLropHwmjotdUyP/ObzqZpXuQKP2Jf9R12vIHKFSStKw==", + "node_modules/portfinder/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/postcss": { + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/csstools" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "@csstools/selector-specificity": "^3.0.2", - "postcss-selector-parser": "^6.0.13" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" + "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-normalize-charset": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz", - "integrity": "sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==", + "node_modules/postcss-calc": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.1.tgz", + "integrity": "sha512-pp1Z3FxtxA+xHAoWXcOXgnBN1WPu4ZiJ5LWGjKyf9MMreagAsaTUtnqFK1y1sHhyJddAkYTPu6XSuLgb3oYCjw==", "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.1.1", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12 || ^20.9 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.38" } }, - "node_modules/postcss-normalize-display-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz", - "integrity": "sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==", + "node_modules/postcss-colormin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.1.tgz", + "integrity": "sha512-uszdT0dULt3FQs47G5UHCduYK+FnkLYlpu1HpWu061eGsKZ7setoG7kA+WC9NQLsOJf69D5TxGHgnAdRgylnFQ==", "dev": true, "dependencies": { + "browserslist": "^4.23.1", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-normalize-positions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz", - "integrity": "sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==", + "node_modules/postcss-convert-values": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.2.tgz", + "integrity": "sha512-MuZIF6HJ4izko07Q0TgW6pClalI4al6wHRNPkFzqQdwAwG7hPn0lA58VZdxyb2Vl5AYjJ1piO+jgF9EnTjQwQQ==", "dev": true, "dependencies": { + "browserslist": "^4.23.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz", - "integrity": "sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==", + "node_modules/postcss-custom-properties": { + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.5.tgz", + "integrity": "sha512-xHg8DTCMfN2nrqs2CQTF+0m5jgnzKL5zrW5Y05KF6xBRO0uDPxiplBm/xcr1o49SLbyJXkMuaRJKhRzkrquKnQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.8", + "@csstools/css-parser-algorithms": "^2.6.0", + "@csstools/css-tokenizer": "^2.2.3", + "@csstools/utilities": "^1.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4" } }, - "node_modules/postcss-normalize-string": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz", - "integrity": "sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==", + "node_modules/postcss-discard-comments": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.1.tgz", + "integrity": "sha512-GVrQxUOhmle1W6jX2SvNLt4kmN+JYhV7mzI6BMnkAWR9DtVvg8e67rrV0NfdWhn7x1zxvzdWkMBPdBDCls+uwQ==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz", - "integrity": "sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==", + "node_modules/postcss-discard-duplicates": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.0.tgz", + "integrity": "sha512-bAnSuBop5LpAIUmmOSsuvtKAAKREB6BBIYStWUTGq8oG5q9fClDMMuY8i4UPI/cEcDx2TN+7PMnXYIId20UVDw==", "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz", + "integrity": "sha512-e+QzoReTZ8IAwhnSdp/++7gBZ/F+nBq9y6PomfwORfP7q9nBpK5AMP64kOt0bA+lShBFbBDcgpJ3X4etHg4lzA==", + "dev": true, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz", + "integrity": "sha512-GmNAzx88u3k2+sBTZrJSDauR0ccpE24omTQCVmaTTZFz1du6AasspjaUPMJ2ud4RslZpoFKyf+6MSPETLojc6w==", + "dev": true, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-normalize-unicode": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.3.tgz", - "integrity": "sha512-T2Bb3gXz0ASgc3ori2dzjv6j/P2IantreaC6fT8tWjqYUiqMAh5jGIkdPwEV2FaucjQlCLeFJDJh2BeSugE1ig==", + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "dev": true, "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=14.0.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.0.0" } }, - "node_modules/postcss-normalize-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz", - "integrity": "sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==", + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.2.0" + "camelcase-css": "^2.0.1" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.21" } }, - "node_modules/postcss-normalize-whitespace": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz", - "integrity": "sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==", + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "postcss-value-parser": "^4.2.0" + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">= 14" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/postcss-ordered-values": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz", - "integrity": "sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==", + "node_modules/postcss-merge-longhand": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.2.tgz", + "integrity": "sha512-06vrW6ZWi9qeP7KMS9fsa9QW56+tIMW55KYqF7X3Ccn+NI2pIgPV6gFfvXTMQ05H90Y5DvnCDPZ2IuHa30PMUg==", "dev": true, "dependencies": { - "cssnano-utils": "^4.0.1", - "postcss-value-parser": "^4.2.0" + "postcss-value-parser": "^4.2.0", + "stylehacks": "^7.0.2" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-reduce-initial": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.3.tgz", - "integrity": "sha512-w4QIR9pEa1N4xMx3k30T1vLZl6udVK2RmNqrDXhBXX9L0mBj2a8ADs8zkbaEH7eUy1m30Wyr5EBgHN31Yq1JvA==", + "node_modules/postcss-merge-rules": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.2.tgz", + "integrity": "sha512-VAR47UNvRsdrTHLe7TV1CeEtF9SJYR5ukIB9U4GZyZOptgtsS20xSxy+k5wMrI3udST6O1XuIn7cjQkg7sDAAw==", "dev": true, "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0" + "browserslist": "^4.23.1", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^5.0.0", + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-reduce-transforms": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz", - "integrity": "sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==", + "node_modules/postcss-minify-font-values": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz", + "integrity": "sha512-2ckkZtgT0zG8SMc5aoNwtm5234eUx1GGFJKf2b1bSp8UflqaeFzR50lid4PfqVI9NtGqJ2J4Y7fwvnP/u1cQog==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "node_modules/postcss-minify-gradients": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz", + "integrity": "sha512-pdUIIdj/C93ryCHew0UgBnL2DtUS3hfFa5XtERrs4x+hmpMYGhbzo6l/Ir5de41O0GaKVpK1ZbDNXSY6GkXvtg==", "dev": true, "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "colord": "^2.9.3", + "cssnano-utils": "^5.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": ">=4" + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/postcss-svgo": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz", - "integrity": "sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==", + "node_modules/postcss-minify-params": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.1.tgz", + "integrity": "sha512-e+Xt8xErSRPgSRFxHeBCSxMiO8B8xng7lh8E0A5ep1VfwYhY8FXhu4Q3APMjgx9YDDbSp53IBGENrzygbUvgUQ==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "browserslist": "^4.23.1", + "cssnano-utils": "^5.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-unique-selectors": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz", - "integrity": "sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==", + "node_modules/postcss-minify-selectors": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.2.tgz", + "integrity": "sha512-dCzm04wqW1uqLmDZ41XYNBJfjgps3ZugDpogAmJXoCb5oCiTzIX4oPXXKxDpTvWOnKxQKR4EbV4ZawJBLcdXXA==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.15" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.4.tgz", + "integrity": "sha512-WuCe0KnP4vKjLZK8VNoUWKL8ZLOv/5jiM94mHcI3VszLropHwmjotdUyP/ObzqZpXuQKP2Jf9R12vIHKFSStKw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-specificity": "^3.0.2", + "postcss-selector-parser": "^6.0.13" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz", + "integrity": "sha512-ABisNUXMeZeDNzCQxPxBCkXexvBrUHV+p7/BXOY+ulxkcjUZO0cp8ekGBwvIh2LbCwnWbyMPNJVtBSdyhM2zYQ==", + "dev": true, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz", + "integrity": "sha512-lnFZzNPeDf5uGMPYgGOw7v0BfB45+irSRz9gHQStdkkhiM0gTfvWkWB5BMxpn0OqgOQuZG/mRlZyJxp0EImr2Q==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz", + "integrity": "sha512-I0yt8wX529UKIGs2y/9Ybs2CelSvItfmvg/DBIjTnoUSrPxSV7Z0yZ8ShSVtKNaV/wAY+m7bgtyVQLhB00A1NQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz", + "integrity": "sha512-o3uSGYH+2q30ieM3ppu9GTjSXIzOrRdCUn8UOMGNw7Af61bmurHTWI87hRybrP6xDHvOe5WlAj3XzN6vEO8jLw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz", + "integrity": "sha512-w/qzL212DFVOpMy3UGyxrND+Kb0fvCiBBujiaONIihq7VvtC7bswjWgKQU/w4VcRyDD8gpfqUiBQ4DUOwEJ6Qg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz", + "integrity": "sha512-tNgw3YV0LYoRwg43N3lTe3AEWZ66W7Dh7lVEpJbHoKOuHc1sLrzMLMFjP8SNULHaykzsonUEDbKedv8C+7ej6g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.1.tgz", + "integrity": "sha512-PTPGdY9xAkTw+8ZZ71DUePb7M/Vtgkbbq+EoI33EuyQEzbKemEQMhe5QSr0VP5UfZlreANDPxSfcdSprENcbsg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz", + "integrity": "sha512-+d7+PpE+jyPX1hDQZYG+NaFD+Nd2ris6r8fPTBAjE8z/U41n/bib3vze8x7rKs5H1uEw5ppe9IojewouHk0klQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz", + "integrity": "sha512-37/toN4wwZErqohedXYqWgvcHUGlT8O/m2jVkAfAe9Bd4MzRqlBmXrJRePH0e9Wgnz2X7KymTgTOaaFizQe3AQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-ordered-values": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz", + "integrity": "sha512-irWScWRL6nRzYmBOXReIKch75RRhNS86UPUAxXdmW/l0FcAsg0lvAXQCby/1lymxn/o0gVa6Rv/0f03eJOwHxw==", + "dev": true, + "dependencies": { + "cssnano-utils": "^5.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.1.tgz", + "integrity": "sha512-0JDUSV4bGB5FGM5g8MkS+rvqKukJZ7OTHw/lcKn7xPNqeaqJyQbUO8/dJpvyTpaVwPsd3Uc33+CfNzdVowp2WA==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.1", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz", + "integrity": "sha512-pnt1HKKZ07/idH8cpATX/ujMbtOGhUfE+m8gbqwJE05aTaNw8gbo34a2e3if0xc0dlu75sUOiqvwCGY3fzOHew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz", + "integrity": "sha512-0WBUlSL4lhD9rA5k1e5D8EN5wCEyZD6HJk0jIvRxl+FDVOMlJ7DePHYWGGVc5QRqrJ3/06FTXM0bxjmJpmTPSA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.3.2" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.1.tgz", + "integrity": "sha512-MH7QE/eKUftTB5ta40xcHLl7hkZjgDFydpfTK+QWXeHxghVt3VoPqYL5/G+zYZPPIs+8GuqFXSTgxBSoB1RZtQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.1.0" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "node_modules/postcss/node_modules/nanoid": { @@ -8795,13 +8824,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.6.0" } }, "node_modules/process-nextick-args": { @@ -8810,25 +8839,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -8875,9 +8885,9 @@ "dev": true }, "node_modules/radix3": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.0.tgz", - "integrity": "sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", "dev": true }, "node_modules/randombytes": { @@ -8899,14 +8909,13 @@ } }, "node_modules/rc9": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.1.tgz", - "integrity": "sha512-lNeOl38Ws0eNxpO3+wD1I9rkHGQyj1NU1jlzv4go2CtEnEQEUfqnIvZG7W+bC/aXdJ27n5x/yUjb6RoT9tko+Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", "dev": true, "dependencies": { - "defu": "^6.1.2", - "destr": "^2.0.0", - "flat": "^5.0.2" + "defu": "^6.1.4", + "destr": "^2.0.3" } }, "node_modules/read-cache": { @@ -8918,92 +8927,29 @@ "pify": "^2.3.0" } }, - "node_modules/read-package-json": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", - "integrity": "sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg==", + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-package-json/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", - "dev": true, - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/read-package-json/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdir-glob": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", - "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", - "dev": true, - "dependencies": { - "minimatch": "^5.1.0" + "minimatch": "^5.1.0" } }, "node_modules/readdirp": { @@ -9261,15 +9207,6 @@ "node": ">= 0.6" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -9280,10 +9217,17 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -9309,6 +9253,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -9338,9 +9283,9 @@ } }, "node_modules/rollup": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", - "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -9353,21 +9298,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.14.0", - "@rollup/rollup-android-arm64": "4.14.0", - "@rollup/rollup-darwin-arm64": "4.14.0", - "@rollup/rollup-darwin-x64": "4.14.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.14.0", - "@rollup/rollup-linux-arm64-gnu": "4.14.0", - "@rollup/rollup-linux-arm64-musl": "4.14.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0", - "@rollup/rollup-linux-riscv64-gnu": "4.14.0", - "@rollup/rollup-linux-s390x-gnu": "4.14.0", - "@rollup/rollup-linux-x64-gnu": "4.14.0", - "@rollup/rollup-linux-x64-musl": "4.14.0", - "@rollup/rollup-win32-arm64-msvc": "4.14.0", - "@rollup/rollup-win32-ia32-msvc": "4.14.0", - "@rollup/rollup-win32-x64-msvc": "4.14.0", + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", "fsevents": "~2.3.2" } }, @@ -9438,13 +9384,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "optional": true - }, "node_modules/sass": { "version": "1.71.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", @@ -9471,13 +9410,10 @@ "dev": true }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -9485,24 +9421,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -9570,12 +9488,12 @@ } }, "node_modules/serve-placeholder": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/serve-placeholder/-/serve-placeholder-2.0.1.tgz", - "integrity": "sha512-rUzLlXk4uPFnbEaIz3SW8VISTxMuONas88nYWjAWaM2W9VDbt9tyFOr3lq8RhVOFrT3XISoBw8vni5una8qMnQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/serve-placeholder/-/serve-placeholder-2.0.2.tgz", + "integrity": "sha512-/TMG8SboeiQbZJWRlfTCqMs2DD3SZgWp0kDQePz9yUuCnDfDh/92gf7/PxGhzXTKBIPASIHxFcZndoNbp6QOLQ==", "dev": true, "dependencies": { - "defu": "^6.0.0" + "defu": "^6.1.4" } }, "node_modules/serve-static": { @@ -9641,32 +9559,15 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/sigstore": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.2.2.tgz", - "integrity": "sha512-2A3WvXkQurhuMgORgT60r6pOWiCOO5LlEqY2ADxGBDGVYLSo5HN0uLtb68YpVpuL/Vi8mLTe7+0Dx2Fq8lLqEg==", - "dev": true, - "dependencies": { - "@sigstore/bundle": "^2.2.0", - "@sigstore/core": "^1.0.0", - "@sigstore/protobuf-specs": "^0.3.0", - "@sigstore/sign": "^2.2.3", - "@sigstore/tuf": "^2.3.1", - "@sigstore/verify": "^1.1.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/simple-git": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.22.0.tgz", - "integrity": "sha512-6JujwSs0ac82jkGjMHiCnTifvf1crOiY/+tfs/Pqih6iow7VrpNKRRNdWm6RtaXpvvv/JGNYhlUtLhGFqHF+Yw==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.25.0.tgz", + "integrity": "sha512-KIY5sBnzc4yEcJXW7Tdv4viEz8KyG+nU0hay+DWZasvdFOYKeUZ6Xc25LUHHjw0tinPT7O1eY6pzX7pRT1K8rw==", "dev": true, "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", - "debug": "^4.3.4" + "debug": "^4.3.5" }, "funding": { "type": "github", @@ -9705,62 +9606,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/smob": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", - "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "dev": true }, - "node_modules/socks": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", - "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", - "dev": true, - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", - "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "socks": "^2.7.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -9798,54 +9649,13 @@ "node": ">=0.10.0" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", - "dev": true - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true - }, - "node_modules/ssri": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz", - "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==", + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "dev": true, - "dependencies": { - "minipass": "^7.0.3" - }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=0.10.0" } }, "node_modules/standard-as-callback": { @@ -9870,13 +9680,14 @@ "dev": true }, "node_modules/streamx": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", - "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" @@ -9978,34 +9789,34 @@ } }, "node_modules/strip-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.0.0.tgz", - "integrity": "sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz", + "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==", "dev": true, "dependencies": { - "js-tokens": "^8.0.2" + "js-tokens": "^9.0.0" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/strip-literal/node_modules/js-tokens": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.3.tgz", - "integrity": "sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", + "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "dev": true }, "node_modules/stylehacks": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.3.tgz", - "integrity": "sha512-KzBqjnqktc8/I0ERCb+lGq06giF/JxDbw2r9kEVhen9noHeIDRtMWUp9r62sOk+/2bbX6sFG1GhsS7ToXG0PEg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.2.tgz", + "integrity": "sha512-HdkWZS9b4gbgYTdMg4gJLmm7biAUug1qTqXjS+u8X+/pUd+9Px1E+520GnOW3rST9MNsVOVpsJG+mPHNosxjOQ==", "dev": true, "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.15" + "browserslist": "^4.23.1", + "postcss-selector-parser": "^6.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { "postcss": "^8.4.31" @@ -10079,6 +9890,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/superjson": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", + "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", + "dev": true, + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -10110,9 +9933,9 @@ "dev": true }, "node_modules/svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "dev": true, "dependencies": { "@trysound/sax": "0.2.0", @@ -10409,6 +10232,15 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10436,6 +10268,15 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -10496,20 +10337,6 @@ "node": ">=0.6.x" } }, - "node_modules/tuf-js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.0.tgz", - "integrity": "sha512-ZSDngmP1z6zw+FIkIBjvOp/II/mIub/O7Pp12j1WNsiCpg5R5wAc//i555bBQsE44O94btLt0xM/Zr2LQjwdCg==", - "dev": true, - "dependencies": { - "@tufjs/models": "2.0.0", - "debug": "^4.3.4", - "make-fetch-happen": "^13.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/type-fest": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", @@ -10536,9 +10363,9 @@ } }, "node_modules/ufo": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz", - "integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", "dev": true }, "node_modules/ultrahtml": { @@ -10590,16 +10417,16 @@ "dev": true }, "node_modules/unenv": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-1.9.0.tgz", - "integrity": "sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-1.10.0.tgz", + "integrity": "sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==", "dev": true, "dependencies": { "consola": "^3.2.3", - "defu": "^6.1.3", + "defu": "^6.1.4", "mime": "^3.0.0", - "node-fetch-native": "^1.6.1", - "pathe": "^1.1.1" + "node-fetch-native": "^1.6.4", + "pathe": "^1.1.2" } }, "node_modules/unenv/node_modules/mime": { @@ -10615,14 +10442,14 @@ } }, "node_modules/unhead": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.8.10.tgz", - "integrity": "sha512-dth8FvZkLriO5ZWWOBIYBNSfGiwJtKcqpPWpSOk/Z0e2jdlgwoZEWZHFyte0EKvmbZxKcsWNMqIuv7dEmS5yZQ==", + "version": "1.9.16", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.9.16.tgz", + "integrity": "sha512-FOoXkuRNDwt7PUaNE0LXNCb6RCz4vTpkGymz4tJ8rcaG5uUJ0lxGK536hzCFwFw3Xkp3n+tkt2yCcbAZE/FOvA==", "dev": true, "dependencies": { - "@unhead/dom": "1.8.10", - "@unhead/schema": "1.8.10", - "@unhead/shared": "1.8.10", + "@unhead/dom": "1.9.16", + "@unhead/schema": "1.9.16", + "@unhead/shared": "1.9.16", "hookable": "^5.5.3" }, "funding": { @@ -10642,60 +10469,24 @@ } }, "node_modules/unimport": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.7.1.tgz", - "integrity": "sha512-V9HpXYfsZye5bPPYUgs0Otn3ODS1mDUciaBlXljI4C2fTwfFpvFZRywmlOu943puN9sncxROMZhsZCjNXEpzEQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.10.0.tgz", + "integrity": "sha512-/UvKRfWx3mNDWwWQhR62HsoM3wxHwYdTq8ellZzMOHnnw4Dp8tovgthyW7DjTrbjDL+i4idOp06voz2VKlvrLw==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.1.0", - "acorn": "^8.11.2", + "acorn": "^8.12.1", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "fast-glob": "^3.3.2", "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "mlly": "^1.4.2", - "pathe": "^1.1.1", - "pkg-types": "^1.0.3", - "scule": "^1.1.1", - "strip-literal": "^1.3.0", - "unplugin": "^1.5.1" - } - }, - "node_modules/unimport/node_modules/strip-literal": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", - "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", - "dev": true, - "dependencies": { - "acorn": "^8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "dev": true, - "dependencies": { - "unique-slug": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "magic-string": "^0.30.11", + "mlly": "^1.7.1", + "pathe": "^1.1.2", + "pkg-types": "^1.1.3", + "scule": "^1.3.0", + "strip-literal": "^2.1.0", + "unplugin": "^1.12.0" } }, "node_modules/universalify": { @@ -10708,15 +10499,18 @@ } }, "node_modules/unplugin": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.7.1.tgz", - "integrity": "sha512-JqzORDAPxxs8ErLV4x+LL7bk5pk3YlcWqpSNsIkAZj972KzFZLClc/ekppahKkOczGkwIG6ElFgdOgOlK4tXZw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.0.tgz", + "integrity": "sha512-KeczzHl2sATPQUx1gzo+EnUkmN4VmGBYRRVOZSGvGITE9rGHRDGqft6ONceP3vgXcyJ2XjX5axG5jMWUwNCYLw==", "dev": true, "dependencies": { - "acorn": "^8.11.3", - "chokidar": "^3.5.3", + "acorn": "^8.12.1", + "chokidar": "^3.6.0", "webpack-sources": "^3.2.3", - "webpack-virtual-modules": "^0.6.1" + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/unplugin-remove": { @@ -10731,27 +10525,27 @@ } }, "node_modules/unplugin-vue-router": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.7.0.tgz", - "integrity": "sha512-ddRreGq0t5vlSB7OMy4e4cfU1w2AwBQCwmvW3oP/0IHQiokzbx4hd3TpwBu3eIAFVuhX2cwNQwp1U32UybTVCw==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.10.2.tgz", + "integrity": "sha512-aG1UzB96cu4Lu+EdQxl22NIKFrde5b+k568JdsaJ2gzPqnQufPk2j1gCA5DxFfGz9zg4tYTqy2A2JHForVbyXg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.19", - "@rollup/pluginutils": "^5.0.4", - "@vue-macros/common": "^1.8.0", - "ast-walker-scope": "^0.5.0", - "chokidar": "^3.5.3", - "fast-glob": "^3.3.1", + "@babel/types": "^7.25.2", + "@rollup/pluginutils": "^5.1.0", + "@vue-macros/common": "^1.11.0", + "ast-walker-scope": "^0.6.1", + "chokidar": "^3.6.0", + "fast-glob": "^3.3.2", "json5": "^2.2.3", - "local-pkg": "^0.4.3", - "mlly": "^1.4.2", - "pathe": "^1.1.1", - "scule": "^1.0.0", - "unplugin": "^1.5.0", - "yaml": "^2.3.2" + "local-pkg": "^0.5.0", + "mlly": "^1.7.1", + "pathe": "^1.1.2", + "scule": "^1.3.0", + "unplugin": "^1.12.0", + "yaml": "^2.5.0" }, "peerDependencies": { - "vue-router": "^4.1.0" + "vue-router": "^4.4.0" }, "peerDependenciesMeta": { "vue-router": { @@ -10759,49 +10553,37 @@ } } }, - "node_modules/unplugin-vue-router/node_modules/local-pkg": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", - "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/unstorage": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.10.1.tgz", - "integrity": "sha512-rWQvLRfZNBpF+x8D3/gda5nUCQL2PgXy2jNG4U7/Rc9BGEv9+CAJd0YyGCROUBKs9v49Hg8huw3aih5Bf5TAVw==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.10.2.tgz", + "integrity": "sha512-cULBcwDqrS8UhlIysUJs2Dk0Mmt8h7B0E6mtR+relW9nZvsf/u4SkAYyNliPiPW7XtFNb5u3IUMkxGxFTTRTgQ==", "dev": true, "dependencies": { "anymatch": "^3.1.3", - "chokidar": "^3.5.3", - "destr": "^2.0.2", - "h3": "^1.8.2", - "ioredis": "^5.3.2", - "listhen": "^1.5.5", - "lru-cache": "^10.0.2", + "chokidar": "^3.6.0", + "destr": "^2.0.3", + "h3": "^1.11.1", + "listhen": "^1.7.2", + "lru-cache": "^10.2.0", "mri": "^1.2.0", - "node-fetch-native": "^1.4.1", + "node-fetch-native": "^1.6.2", "ofetch": "^1.3.3", - "ufo": "^1.3.1" + "ufo": "^1.4.0" }, "peerDependencies": { - "@azure/app-configuration": "^1.4.1", + "@azure/app-configuration": "^1.5.0", "@azure/cosmos": "^4.0.0", "@azure/data-tables": "^13.2.2", - "@azure/identity": "^3.3.2", - "@azure/keyvault-secrets": "^4.7.0", - "@azure/storage-blob": "^12.16.0", - "@capacitor/preferences": "^5.0.6", - "@netlify/blobs": "^6.2.0", - "@planetscale/database": "^1.11.0", - "@upstash/redis": "^1.23.4", - "@vercel/kv": "^0.2.3", - "idb-keyval": "^6.2.1" + "@azure/identity": "^4.0.1", + "@azure/keyvault-secrets": "^4.8.0", + "@azure/storage-blob": "^12.17.0", + "@capacitor/preferences": "^5.0.7", + "@netlify/blobs": "^6.5.0 || ^7.0.0", + "@planetscale/database": "^1.16.0", + "@upstash/redis": "^1.28.4", + "@vercel/kv": "^1.0.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.3.2" }, "peerDependenciesMeta": { "@azure/app-configuration": { @@ -10839,17 +10621,17 @@ }, "idb-keyval": { "optional": true + }, + "ioredis": { + "optional": true } } }, "node_modules/unstorage/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/untun": { "version": "0.1.3", @@ -10884,22 +10666,23 @@ } }, "node_modules/unwasm": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/unwasm/-/unwasm-0.3.7.tgz", - "integrity": "sha512-+s4iWvHHYnLuwNo+9mqVFLBmBzGc3gIuzkVZ8fdMN9K/kWopCnfaUVnDagd2OX3It5nRR5EenI5nSQb8FOd0fA==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/unwasm/-/unwasm-0.3.9.tgz", + "integrity": "sha512-LDxTx/2DkFURUd+BU1vUsF/moj0JsoTvl+2tcg2AUOiEzVturhGGx17/IMgGvKUYdZwr33EJHtChCJuhu9Ouvg==", "dev": true, "dependencies": { - "magic-string": "^0.30.5", - "mlly": "^1.5.0", + "knitwork": "^1.0.0", + "magic-string": "^0.30.8", + "mlly": "^1.6.1", "pathe": "^1.1.2", "pkg-types": "^1.0.3", - "unplugin": "^1.6.0" + "unplugin": "^1.10.0" } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -10916,8 +10699,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -10944,28 +10727,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", - "dev": true, - "dependencies": { - "builtins": "^5.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -10976,13 +10737,13 @@ } }, "node_modules/vite": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", - "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", + "integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", + "esbuild": "^0.21.3", + "postcss": "^8.4.39", "rollup": "^4.13.0" }, "bin": { @@ -11030,16 +10791,28 @@ } } }, + "node_modules/vite-hot-client": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.3.tgz", + "integrity": "sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0" + } + }, "node_modules/vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", + "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", "dev": true, "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", "vite": "^5.0.0" }, "bin": { @@ -11053,9 +10826,9 @@ } }, "node_modules/vite-plugin-checker": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.4.tgz", - "integrity": "sha512-2zKHH5oxr+ye43nReRbC2fny1nyARwhxdm0uNYp/ERy4YvU9iZpNOsueoi/luXw5gnpqRSvjcEPxXbS153O2wA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.7.2.tgz", + "integrity": "sha512-xeYeJbG0gaCaT0QcUC4B2Zo4y5NR8ZhYenc5gPbttrZvraRFwkEADCYwq+BfEHl9zYz7yf85TxsiGoYwyyIjhw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", @@ -11066,7 +10839,6 @@ "fast-glob": "^3.2.7", "fs-extra": "^11.1.0", "npm-run-path": "^4.0.1", - "semver": "^7.5.0", "strip-ansi": "^6.0.0", "tiny-invariant": "^1.1.0", "vscode-languageclient": "^7.0.0", @@ -11078,6 +10850,7 @@ "node": ">=14.16" }, "peerDependencies": { + "@biomejs/biome": ">=1.7", "eslint": ">=7", "meow": "^9.0.0", "optionator": "^0.9.1", @@ -11086,9 +10859,12 @@ "vite": ">=2.0.0", "vls": "*", "vti": "*", - "vue-tsc": ">=1.3.9" + "vue-tsc": ">=2.0.0" }, "peerDependenciesMeta": { + "@biomejs/biome": { + "optional": true + }, "eslint": { "optional": true }, @@ -11115,176 +10891,582 @@ } } }, - "node_modules/vite-plugin-checker/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/vite-plugin-checker/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vite-plugin-checker/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/vite-plugin-checker/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vite-plugin-checker/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/vite-plugin-checker/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/vite-plugin-checker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/vite-plugin-checker/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vite-plugin-checker/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.5.tgz", + "integrity": "sha512-JvTUqsP1JNDw0lMZ5Z/r5cSj81VK2B7884LO1DC3GMBhdcjcsAnJjdWq7bzQL01Xbh+v60d3lju3g+z7eAtNew==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.0", + "debug": "^4.3.5", + "error-stack-parser-es": "^0.1.4", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.0.1", + "sirv": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-inspect/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.1.3.tgz", + "integrity": "sha512-pMrseXIDP1Gb38mOevY+BvtNGNqiqmqa2pKB99lnLsADQww9w9xMbAfT4GB6RUoaOkSPrtlXqpq2Fq+Dj2AgFg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/vite-plugin-checker/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=12" } }, - "node_modules/vite-plugin-checker/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/vite-plugin-checker/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/vite-plugin-checker/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 12" + "node": ">=12" } }, - "node_modules/vite-plugin-checker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/vite-plugin-checker/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/vite-plugin-checker/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/vite-plugin-inspect": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.3.tgz", - "integrity": "sha512-SBVzOIdP/kwe6hjkt7LSW4D0+REqqe58AumcnCfRNw4Kt3mbS9pEBkch+nupu2PBxv2tQi69EQHQ1ZA1vgB/Og==", + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@antfu/utils": "^0.7.7", - "@rollup/pluginutils": "^5.1.0", - "debug": "^4.3.4", - "error-stack-parser-es": "^0.1.1", - "fs-extra": "^11.2.0", - "open": "^10.0.3", - "perfect-debounce": "^1.0.0", - "picocolors": "^1.0.0", - "sirv": "^2.0.4" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - } + "node": ">=12" } }, - "node_modules/vite-plugin-inspect/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "sunos" + ], "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/vite-plugin-inspect/node_modules/open": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/open/-/open-10.0.4.tgz", - "integrity": "sha512-oujJ/FFr7ra6/7gJuQ4ZJJ8Gf2VHM0J3J/W7IvH++zaqEzacWVxzK++NiVY5NLHTTj7u/jNH5H3Ei9biL31Lng==", + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/vite-plugin-vue-inspector": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-4.0.2.tgz", - "integrity": "sha512-KPvLEuafPG13T7JJuQbSm5PwSxKFnVS965+MP1we2xGw9BPkkc/+LPix5MMWenpKWqtjr0ws8THrR+KuoDC8hg==", + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], "dev": true, - "dependencies": { - "@babel/core": "^7.23.0", - "@babel/plugin-proposal-decorators": "^7.23.0", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-typescript": "^7.22.15", - "@vue/babel-plugin-jsx": "^1.1.5", - "@vue/compiler-dom": "^3.3.4", - "kolorist": "^1.8.0", - "magic-string": "^0.30.4" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, - "peerDependencies": { - "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0" + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/vscode-jsonrpc": { @@ -11355,9 +11537,9 @@ } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", - "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", "dev": true }, "node_modules/vscode-languageserver-types": { @@ -11373,16 +11555,16 @@ "dev": true }, "node_modules/vue": { - "version": "3.4.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", - "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.36.tgz", + "integrity": "sha512-mIFvbLgjODfx3Iy1SrxOsiPpDb8Bo3EU+87ioimOZzZTOp15IEdAels70IjBOLO3ZFlLW5AhdwY4dWbXVQKYow==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-sfc": "3.4.21", - "@vue/runtime-dom": "3.4.21", - "@vue/server-renderer": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.4.36", + "@vue/compiler-sfc": "3.4.36", + "@vue/runtime-dom": "3.4.36", + "@vue/server-renderer": "3.4.36", + "@vue/shared": "3.4.36" }, "peerDependencies": { "typescript": "*" @@ -11394,12 +11576,12 @@ } }, "node_modules/vue-bundle-renderer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-2.0.0.tgz", - "integrity": "sha512-oYATTQyh8XVkUWe2kaKxhxKVuuzK2Qcehe+yr3bGiaQAhK3ry2kYE4FWOfL+KO3hVFwCdLmzDQTzYhTi9C+R2A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vue-bundle-renderer/-/vue-bundle-renderer-2.1.0.tgz", + "integrity": "sha512-uZ+5ZJdZ/b43gMblWtcpikY6spJd0nERaM/1RtgioXNfWFbjKlUwrS8HlrddN6T2xtptmOouWclxLUkpgcVX3Q==", "dev": true, "dependencies": { - "ufo": "^1.2.0" + "ufo": "^1.5.3" } }, "node_modules/vue-devtools-stub": { @@ -11409,12 +11591,12 @@ "dev": true }, "node_modules/vue-router": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz", - "integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.3.tgz", + "integrity": "sha512-sv6wmNKx2j3aqJQDMxLFzs/u/mjA9Z5LCgy6BE0f7yFWMjrPLnS/sPNn8ARY/FXw6byV18EFutn5lTO6+UsV5A==", "dev": true, "dependencies": { - "@vue/devtools-api": "^6.5.1" + "@vue/devtools-api": "^6.6.3" }, "funding": { "url": "https://github.com/sponsors/posva" @@ -11439,9 +11621,9 @@ } }, "node_modules/webpack-virtual-modules": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", - "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true }, "node_modules/whatwg-url": { @@ -11586,9 +11768,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -11644,9 +11826,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz", - "integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", "dev": true, "bin": { "yaml": "bin.mjs" @@ -11701,17 +11883,17 @@ } }, "node_modules/zip-stream": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.2.tgz", - "integrity": "sha512-LfOdrUvPB8ZoXtvOBz6DlNClfvi//b5d56mSWyJi7XbH/HfhOHfUhOqxhT/rUiR7yiktlunqRo+jY6y/cWC/5g==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "dependencies": { - "archiver-utils": "^4.0.1", - "compress-commons": "^5.0.1", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14" } } } diff --git a/sechub-website/package.json b/sechub-website/package.json index 0e11d41227..f54e9e0745 100644 --- a/sechub-website/package.json +++ b/sechub-website/package.json @@ -16,7 +16,7 @@ "@heroicons/vue": "^2.1.1", "@nuxt/devtools": "latest", "@nuxtjs/tailwindcss": "^6.11.4", - "nuxt": "^3.10.3", + "nuxt": "^3.12.4", "nuxt-security": "^1.2.1", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11" diff --git a/sechub-webui/src/main/java/com/mercedesbenz/sechub/webui/page/user/UserDetailInformationService.java b/sechub-webui/src/main/java/com/mercedesbenz/sechub/webui/page/user/UserDetailInformationService.java index 83c8b4d068..3400bbc5f5 100644 --- a/sechub-webui/src/main/java/com/mercedesbenz/sechub/webui/page/user/UserDetailInformationService.java +++ b/sechub-webui/src/main/java/com/mercedesbenz/sechub/webui/page/user/UserDetailInformationService.java @@ -3,12 +3,15 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service public class UserDetailInformationService { public UserDetails getUser() { /* FIXME Albert Tregnaghi, 2024-02-28:implement real user management */ - return User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build(); + PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); + return User.builder().passwordEncoder(encoder::encode).username("user").password("password").roles("USER").build(); } } \ No newline at end of file diff --git a/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/cli/CheckmarxWrapperCLI.java b/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/cli/CheckmarxWrapperCLI.java index 756487090d..0de95a4eb4 100644 --- a/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/cli/CheckmarxWrapperCLI.java +++ b/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/cli/CheckmarxWrapperCLI.java @@ -31,7 +31,6 @@ public void run(String... args) throws Exception { storageService.store(result); } catch (Exception e) { - System.out.println("Execution failed - " + e.getMessage()); LOG.error("Execution failed", e); System.exit(2); diff --git a/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageService.java b/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageService.java index 8b646d3b5b..dcb885e9fc 100644 --- a/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageService.java +++ b/sechub-wrapper-checkmarx/src/main/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageService.java @@ -38,7 +38,7 @@ private void writeProductResult(AdapterExecutionResult adapterResult) throws IOE } File pdsResultFile = new File(pdsResultFilePath); - writer.save(pdsResultFile, adapterResult.getProductResult(), true); + writer.writeTextToFile(pdsResultFile, adapterResult.getProductResult(), true); } private void writeProductMessages(AdapterExecutionResult adapterResult) throws IOException { diff --git a/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperScanContextTest.java b/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperScanContextTest.java index 02408ed449..de1cea6b42 100644 --- a/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperScanContextTest.java +++ b/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperScanContextTest.java @@ -77,7 +77,7 @@ void created_source_input_stream_can_be_read_and_contains_data_of_recompressed_f Path testFolder = TestUtil.createTempDirectoryInBuildFolder("chmx-wrapper-input-stream"); File extractedFolder = new File(testFolder.toFile(), "extracted"); extractedFolder.mkdirs(); - writer.save(new File(extractedFolder, "at-least-one-file.txt"), "content", false); + writer.writeTextToFile(new File(extractedFolder, "at-least-one-file.txt"), "content", false); File expectedCompressFolder = new File(extractedFolder.getParentFile(), "recompressed"); File expectedTargetFile = new File(expectedCompressFolder, CommonConstants.FILENAME_SOURCECODE_ZIP); @@ -93,7 +93,7 @@ void created_source_input_stream_can_be_read_and_contains_data_of_recompressed_f public Void answer(InvocationOnMock invocation) throws Throwable { // we simulate that archive support writes a "ZIP" file - instead of doing // a real compression the mock writes a single line into the output file. - writer.save(expectedTargetFile, singleLineInWrittenFile, false); + writer.writeTextToFile(expectedTargetFile, singleLineInWrittenFile, false); return null; } }).when(archiveSupport).compressFolder(eq(ArchiveType.ZIP), eq(extractedFolder), eq(expectedTargetFile)); diff --git a/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageServiceTest.java b/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageServiceTest.java index d1ff726056..18eba451a2 100644 --- a/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageServiceTest.java +++ b/sechub-wrapper-checkmarx/src/test/java/com/mercedesbenz/sechub/wrapper/checkmarx/scan/CheckmarxWrapperStorageServiceTest.java @@ -57,7 +57,7 @@ void service_stores_product_result_as_file_at_defined_location_by_writer() throw serviceToTest.store(result); /* test */ - verify(writer).save(resultFile, "content", true); + verify(writer).writeTextToFile(resultFile, "content", true); } @@ -75,7 +75,7 @@ void service_stores_product_messages_and_result_and_writes_product_messages() th serviceToTest.store(result); /* test */ - verify(writer).save(resultFile, "content1", true); + verify(writer).writeTextToFile(resultFile, "content1", true); verify(messageSupport).writeMessages(eq(Arrays.asList(message1, message2))); } diff --git a/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/RuleProvider.java b/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/RuleProvider.java index 4dc48c8b3f..ae5d732708 100644 --- a/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/RuleProvider.java +++ b/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/RuleProvider.java @@ -54,7 +54,7 @@ public DeactivatedRuleReferences fetchDeactivatedRuleReferences(File rulesDeactv private String readFileContent(File file) { try { - return reader.loadTextFile(file); + return reader.readTextFromFile(file); } catch (IOException e) { throw new ZapWrapperRuntimeException("Error reading file: " + file, e, ZapWrapperExitCode.IO_ERROR); } diff --git a/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/SecHubScanConfigProvider.java b/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/SecHubScanConfigProvider.java index 9c483f3959..e33fec6c45 100644 --- a/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/SecHubScanConfigProvider.java +++ b/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/config/SecHubScanConfigProvider.java @@ -19,7 +19,7 @@ public SecHubScanConfiguration getSecHubWebConfiguration(File secHubConfigFile) String sechubConfigJson; SecHubScanConfiguration sechubScanConfig; try { - sechubConfigJson = fileReader.loadTextFile(secHubConfigFile); + sechubConfigJson = fileReader.readTextFromFile(secHubConfigFile); sechubScanConfig = SecHubScanConfiguration.createFromJSON(sechubConfigJson); } catch (IOException e) { throw new ZapWrapperRuntimeException("Was not able to read sechub config file: " + secHubConfigFile, e, ZapWrapperExitCode.IO_ERROR); diff --git a/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScanner.java b/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScanner.java index 0ca1232b18..1284a9feea 100644 --- a/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScanner.java +++ b/sechub-wrapper-owasp-zap/src/main/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScanner.java @@ -815,7 +815,7 @@ private String readHeaderValueFromFile(HTTPHeaderConfiguration httpHeader) { headerFile = scanContext.getHeaderValueFiles().getOrDefault(httpHeader.getName(), null); try { if (headerFile != null) { - return fileReader.loadTextFile(headerFile.getAbsoluteFile()); + return fileReader.readTextFromFile(headerFile.getAbsoluteFile()); } } catch (IOException e) { SecHubMessage message = new SecHubMessage(SecHubMessageType.ERROR, "Could not read header value from file: " + headerFile); diff --git a/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/helper/SecHubWebScanConfigurationHelperTest.java b/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/helper/SecHubWebScanConfigurationHelperTest.java index 2a95c6c398..e2d45ba707 100644 --- a/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/helper/SecHubWebScanConfigurationHelperTest.java +++ b/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/helper/SecHubWebScanConfigurationHelperTest.java @@ -38,7 +38,7 @@ void determines_AuthenticationType_sechub_config_is_null() { void determines_AuthenticationType_sechub_config_has_basic_auth() { /* prepare */ File file = new File("src/test/resources/sechub-config-examples/basic-auth.json"); - String sechubConfigJSON = TestFileReader.loadTextFile(file); + String sechubConfigJSON = TestFileReader.readTextFromFile(file); SecHubScanConfiguration sechubConfig = SecHubScanConfiguration.createFromJSON(sechubConfigJSON); SecHubWebScanConfiguration secHubWebScanConfiguration = sechubConfig.getWebScan().get(); diff --git a/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScannerTest.java b/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScannerTest.java index 9ad2f0e4eb..1b3d4b6302 100644 --- a/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScannerTest.java +++ b/sechub-wrapper-owasp-zap/src/test/java/com/mercedesbenz/sechub/zapwrapper/scan/ZapScannerTest.java @@ -138,7 +138,7 @@ void deactivate_rules_results_in_rules_are_deactivated() throws ClientApiExcepti deactivatedReferences.addRuleReference(new RuleReference("Cross-Site-Scripting-(Reflected)-40012", "second-info")); deactivatedReferences.addRuleReference(new RuleReference("Path-Traversal-6", "third-info")); - String json = TestFileReader.loadTextFile("src/test/resources/zap-available-rules/zap-full-ruleset.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/zap-available-rules/zap-full-ruleset.json"); ZapFullRuleset ruleSet = new ZapFullRuleset().fromJSON(json); when(clientApiFacade.disablePassiveScannerRule(any())).thenReturn(null); @@ -319,7 +319,7 @@ void add_replacer_rules_for_headers_with_data_section_results_add_replacer_rule_ void set_includes_and_excludes_api_facade_is_called_once_for_each_include_and_once_for_exclude(String sechubConfigFile) throws ClientApiException, MalformedURLException { /* prepare */ - String json = TestFileReader.loadTextFile(sechubConfigFile); + String json = TestFileReader.readTextFromFile(sechubConfigFile); SecHubWebScanConfiguration sechubWebScanConfig = SecHubScanConfiguration.createFromJSON(json).getWebScan().get(); IncludeExcludeToZapURLHelper helper = new IncludeExcludeToZapURLHelper(); @@ -370,7 +370,7 @@ void import_openapi_file_but_api_file_is_null_api_facade_is_never_called() throw void import_openapi_file_api_facade_is_called_once(String sechubConfigFile) throws ClientApiException { /* prepare */ String contextId = "context-id"; - String json = TestFileReader.loadTextFile(sechubConfigFile); + String json = TestFileReader.readTextFromFile(sechubConfigFile); SecHubWebScanConfiguration sechubWebScanConfig = SecHubScanConfiguration.createFromJSON(json).getWebScan().get(); List apiFiles = new ArrayList<>(); @@ -394,7 +394,7 @@ void import_openapi_file_api_facade_is_called_once(String sechubConfigFile) thro void import_openapi_defintion_from_url_api_facade_is_called_once(String sechubConfigFile) throws ClientApiException { /* prepare */ String contextId = "context-id"; - String json = TestFileReader.loadTextFile(sechubConfigFile); + String json = TestFileReader.readTextFromFile(sechubConfigFile); SecHubWebScanConfiguration sechubWebScanConfig = SecHubScanConfiguration.createFromJSON(json).getWebScan().get(); when(scanContext.getSecHubWebScanConfiguration()).thenReturn(sechubWebScanConfig); @@ -415,7 +415,7 @@ void import_openapi_defintion_from_url_api_facade_is_called_once(String sechubCo void import_openapi_from_file_and_from_url_api_facade_is_called_once(String sechubConfigFile) throws ClientApiException { /* prepare */ String contextId = "context-id"; - String json = TestFileReader.loadTextFile(sechubConfigFile); + String json = TestFileReader.readTextFromFile(sechubConfigFile); SecHubWebScanConfiguration sechubWebScanConfig = SecHubScanConfiguration.createFromJSON(json).getWebScan().get(); List apiFiles = new ArrayList<>(); @@ -553,7 +553,7 @@ void import_client_certificate_file_but_without_password_api_facade_is_called_on void configure_login_inside_zap_using_no_auth_and_unsupported_auth_return_null(String sechubConfigFile) throws ClientApiException { /* prepare */ String contextId = "context-id"; - String json = TestFileReader.loadTextFile(sechubConfigFile); + String json = TestFileReader.readTextFromFile(sechubConfigFile); SecHubWebScanConfiguration sechubWebScanConfig = SecHubScanConfiguration.createFromJSON(json).getWebScan().get(); when(scanContext.getSecHubWebScanConfiguration()).thenReturn(sechubWebScanConfig); @@ -571,7 +571,7 @@ void configure_login_inside_zap_using_basic_auth_results_in_expected_calls() thr String contextId = "context-id"; String userId = "user-id"; URL targetUrl = URI.create("https:127.0.0.1:8000").toURL(); - String json = TestFileReader.loadTextFile("src/test/resources/sechub-config-examples/basic-auth.json"); + String json = TestFileReader.readTextFromFile("src/test/resources/sechub-config-examples/basic-auth.json"); SecHubWebScanConfiguration sechubWebScanConfig = SecHubScanConfiguration.createFromJSON(json).getWebScan().get(); BasicLoginConfiguration basicLoginConfiguration = sechubWebScanConfig.getLogin().get().getBasic().get(); String userName = new String(basicLoginConfiguration.getUser()); diff --git a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultService.java b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultService.java index 47dda2d19b..3dbf317ecd 100644 --- a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultService.java +++ b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultService.java @@ -36,7 +36,7 @@ private void writeProductResult(AdapterExecutionResult adapterResult) throws IOE } File pdsResultFile = new File(pdsResultFilePath); - writer.save(pdsResultFile, adapterResult.getProductResult(), true); + writer.writeTextToFile(pdsResultFile, adapterResult.getProductResult(), true); } private void writeProductMessages(AdapterExecutionResult adapterResult) throws IOException { diff --git a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/AbstractPrepareWrapperModule.java b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/AbstractPrepareWrapperModule.java index 2ba00217e8..9c1a4bd67e 100644 --- a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/AbstractPrepareWrapperModule.java +++ b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/AbstractPrepareWrapperModule.java @@ -31,7 +31,7 @@ protected void ensureDirectoryExists(Path path) { try { Files.createDirectories(path); } catch (IOException e) { - throw new RuntimeException("Error while creating download directory: " + path, e); + throw new IllegalStateException("Error while creating download directory: " + path, e); } } diff --git a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/PrepareWrapperModule.java b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/PrepareWrapperModule.java index f43f2cc323..60ca9edffb 100644 --- a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/PrepareWrapperModule.java +++ b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/PrepareWrapperModule.java @@ -3,11 +3,8 @@ import java.io.IOException; -import org.springframework.stereotype.Service; - import com.mercedesbenz.sechub.wrapper.prepare.PrepareWrapperContext; -@Service public interface PrepareWrapperModule { /** diff --git a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModule.java b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModule.java index 016c829339..56a2545c98 100644 --- a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModule.java +++ b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModule.java @@ -16,7 +16,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.commons.model.SecHubMessage; import com.mercedesbenz.sechub.commons.model.SecHubMessageType; @@ -31,7 +31,7 @@ import com.mercedesbenz.sechub.wrapper.prepare.upload.PrepareWrapperUploadException; import com.mercedesbenz.sechub.wrapper.prepare.upload.PrepareWrapperUploadService; -@Service +@Component public class GitPrepareWrapperModule extends AbstractPrepareWrapperModule { private static final Logger LOG = LoggerFactory.getLogger(GitPrepareWrapperModule.class); @@ -157,7 +157,7 @@ protected void assertDownloadSuccessful(GitContext gitContext) { } else { LOG.error("Download of git repository was not successful. Git download directory is not a directory: {}", path); - throw new RuntimeException("Download of git repository was not successful. Git download directory is not a directory."); + throw new IllegalStateException("Download of git repository was not successful. Git download directory is not a directory."); } } diff --git a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModule.java b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModule.java index 210c676a6f..5356e97bdd 100644 --- a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModule.java +++ b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModule.java @@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.commons.model.SecHubRemoteCredentialConfiguration; import com.mercedesbenz.sechub.commons.model.SecHubRemoteCredentialUserData; @@ -28,7 +28,7 @@ import com.mercedesbenz.sechub.wrapper.prepare.upload.PrepareWrapperUploadException; import com.mercedesbenz.sechub.wrapper.prepare.upload.PrepareWrapperUploadService; -@Service +@Component public class SkopeoPrepareWrapperModule extends AbstractPrepareWrapperModule { private static final Logger LOG = LoggerFactory.getLogger(SkopeoPrepareWrapperModule.class); diff --git a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/test/IntegrationTestPrepareWrapperModule.java b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/test/IntegrationTestPrepareWrapperModule.java index bf59ab7e47..e7888f8f36 100644 --- a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/test/IntegrationTestPrepareWrapperModule.java +++ b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/modules/test/IntegrationTestPrepareWrapperModule.java @@ -8,7 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.commons.TextFileWriter; import com.mercedesbenz.sechub.commons.model.SecHubRemoteDataConfiguration; @@ -17,7 +17,7 @@ import com.mercedesbenz.sechub.wrapper.prepare.modules.AbstractPrepareWrapperModule; import com.mercedesbenz.sechub.wrapper.prepare.upload.PrepareWrapperUploadService; -@Service +@Component @Profile(PDSProfiles.INTEGRATIONTEST) /** * Special integration test prepare wrapper module. Is always active in profile @@ -108,7 +108,7 @@ private void createIntegrationTestFiles(IntegrationTestContext integrationTestCo INFO:i am just an information from IntegrationTestPrepareWrapperModule """; - writer.save(dataFile.toFile(), mediumIntegrationTestData, false); + writer.writeTextToFile(dataFile.toFile(), mediumIntegrationTestData, false); } catch (IOException e) { throw new RuntimeException("Error while files in directory: " + downloadPath, e); diff --git a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreator.java b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreator.java index 58cfddbe54..67cacf302c 100644 --- a/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreator.java +++ b/sechub-wrapper-prepare/src/main/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreator.java @@ -4,14 +4,14 @@ import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.commons.archive.ArchiveSupport; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; import com.mercedesbenz.sechub.wrapper.prepare.PrepareWrapperContext; import com.mercedesbenz.sechub.wrapper.prepare.modules.PrepareToolContext; -@Service +@Component public class PrepareWrapperArchiveCreator { @Autowired diff --git a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultServiceTest.java b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultServiceTest.java index 377b1170e4..b856be9183 100644 --- a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultServiceTest.java +++ b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/PrepareWrapperResultServiceTest.java @@ -55,7 +55,7 @@ void service_stores_product_result_as_file_at_defined_location_by_writer() throw serviceToTest.store(result); /* test */ - verify(writer).save(resultFile, "content", true); + verify(writer).writeTextToFile(resultFile, "content", true); } @Test @@ -72,7 +72,7 @@ void service_stores_product_messages_and_result_and_writes_product_messages() th serviceToTest.store(result); /* test */ - verify(writer).save(resultFile, "content1", true); + verify(writer).writeTextToFile(resultFile, "content1", true); verify(messageSupport).writeMessages(eq(Arrays.asList(message1, message2))); } } \ No newline at end of file diff --git a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModuleTest.java b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModuleTest.java index b872440eab..70525f1ec5 100644 --- a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModuleTest.java +++ b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/git/GitPrepareWrapperModuleTest.java @@ -100,7 +100,7 @@ void prepare_successful_with_user_credentials_configured() throws IOException { Path repository = tempDir.toPath().resolve(gitDownloadFolder).resolve(testRepo); Path gitFile = repository.resolve(".git"); - writer.save(gitFile.toFile(), "some text", true); + writer.writeTextToFile(gitFile.toFile(), "some text", true); List subfolder = new ArrayList<>(); subfolder.add(repository); @@ -150,7 +150,7 @@ void prepare_successful_when_no_credentials_are_configured() throws IOException Path repository = tempDir.toPath().resolve(gitDownloadFolder).resolve(testRepo); Path gitFile = repository.resolve(".git"); - writer.save(gitFile.toFile(), "some text", true); + writer.writeTextToFile(gitFile.toFile(), "some text", true); List subfolder = new ArrayList<>(); subfolder.add(repository); @@ -206,7 +206,7 @@ void isDownloadSuccessful_returns_true_when_git_file_in_directory() throws IOExc Path repository = tempDir.toPath().resolve(testRepo); Path gitFile = repository.resolve(".git"); - writer.save(gitFile.toFile(), "some text", true); + writer.writeTextToFile(gitFile.toFile(), "some text", true); GitContext context = mock(GitContext.class); when(context.getToolDownloadDirectory()).thenReturn(tempDir.toPath()); @@ -228,7 +228,7 @@ void isDownloadSuccessful_returns_false_when_no_git_file_in_directory() throws I Path repository = tempDir.toPath().resolve(testRepo); Path javaFile = repository.resolve("class.java"); - writer.save(javaFile.toFile(), "some text", true); + writer.writeTextToFile(javaFile.toFile(), "some text", true); GitContext context = mock(GitContext.class); when(context.getToolDownloadDirectory()).thenReturn(tempDir.toPath()); diff --git a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModuleTest.java b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModuleTest.java index 0559ff4cc4..408962e8d9 100644 --- a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModuleTest.java +++ b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/modules/skopeo/SkopeoPrepareWrapperModuleTest.java @@ -116,7 +116,7 @@ void prepare_successful_when_user_credentials_are_configured_correctly() throws Path testFile = Path.of("testimage.tar"); Path downloadDirectory = tempDir.resolve(SkopeoWrapperConstants.DOWNLOAD_DIRECTORY_NAME); - writer.save(downloadDirectory.resolve(testFile).toFile(), "some text", true); + writer.writeTextToFile(downloadDirectory.resolve(testFile).toFile(), "some text", true); PrepareWrapperEnvironment environment = mock(PrepareWrapperEnvironment.class); when(environment.getPdsJobWorkspaceLocation()).thenReturn(tempDir.toString()); @@ -164,7 +164,7 @@ void prepare_does_validate_download_and_cleanup_docker_image_when_no_credentials Path downloadDirectory = tempDir.resolve(SkopeoWrapperConstants.DOWNLOAD_DIRECTORY_NAME); Path testFile = downloadDirectory.resolve("testimage.tar"); - writer.save(testFile.toFile(), "some text", true); + writer.writeTextToFile(testFile.toFile(), "some text", true); PrepareWrapperEnvironment environment = mock(PrepareWrapperEnvironment.class); when(environment.getPdsJobWorkspaceLocation()).thenReturn(tempDir.toString()); diff --git a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/FileNameSupportTest.java b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/FileNameSupportTest.java index 6fefa6fd8f..07b285a847 100644 --- a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/FileNameSupportTest.java +++ b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/FileNameSupportTest.java @@ -34,7 +34,7 @@ void getRepositoriesFromDirectory_returns_subfolder() throws IOException { String subfolder = "/subfolder"; String file = subfolder + "/test"; - writer.save(new File(tempDir + file), "testText", true); + writer.writeTextToFile(new File(tempDir + file), "testText", true); /* execute */ List result = fileNameSupportToTest.getRepositoriesFromDirectory(tempDir.toPath()); @@ -54,8 +54,8 @@ void getRepositoriesFromDirectory_returns_multiple_directories() throws IOExcept String path = tempDir.getAbsolutePath(); String subfolder1 = "/subfolder"; String subfolder2 = "/subfolder2"; - writer.save(new File(path + subfolder1 + "/hello"), "testText", true); - writer.save(new File(path + subfolder2 + "/hello"), "testText", true); + writer.writeTextToFile(new File(path + subfolder1 + "/hello"), "testText", true); + writer.writeTextToFile(new File(path + subfolder2 + "/hello"), "testText", true); /* execute */ List result = fileNameSupportToTest.getRepositoriesFromDirectory(tempDir.toPath()); @@ -85,7 +85,7 @@ void getTarFilesFromDirectory_returns_tar_file() throws IOException { String path = tempDir.getAbsolutePath(); String tarFile = "/test-tar-file.tar"; - writer.save(new File(path + tarFile), "testText", true); + writer.writeTextToFile(new File(path + tarFile), "testText", true); /* execute */ List result = fileNameSupportToTest.getTarFilesFromDirectory(tempDir.toPath()); @@ -105,8 +105,8 @@ void getTarFilesFromDirectory_returns_multiple_tar_files() throws IOException { String path = tempDir.getAbsolutePath(); String tarFile1 = "/test-tar-file1.tar"; String tarFile2 = "/test-tar-file2.tar"; - writer.save(new File(path + tarFile1), "testText", true); - writer.save(new File(path + tarFile2), "testText", true); + writer.writeTextToFile(new File(path + tarFile1), "testText", true); + writer.writeTextToFile(new File(path + tarFile2), "testText", true); /* execute */ List result = fileNameSupportToTest.getTarFilesFromDirectory(tempDir.toPath()); diff --git a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreatorTest.java b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreatorTest.java index 9dd193bbc9..dfc476e843 100644 --- a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreatorTest.java +++ b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperArchiveCreatorTest.java @@ -53,9 +53,9 @@ void createArchive_creates_archive_for_binary() throws IOException { Path uploadDirectory = tempDir.toPath().resolve(Path.of("upload")); Path testDownload = tempDir.toPath().resolve(Path.of("test-download")); Path testTarFilename = Path.of("test-tar.tar"); - writer.save(testDownload.resolve(testTarFilename).toFile(), "testText", true); + writer.writeTextToFile(testDownload.resolve(testTarFilename).toFile(), "testText", true); - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_data_config_binary_code_scan_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_data_config_binary_code_scan_example.json")); PrepareWrapperContext context = mock(PrepareWrapperContext.class); PrepareWrapperEnvironment environment = mock(PrepareWrapperEnvironment.class); @@ -88,9 +88,9 @@ void createArchive_creates_archive_for_source() throws IOException { Path uploadDirectory = tempDir.toPath().resolve(Path.of("upload")); Path testDownload = tempDir.toPath().resolve(Path.of("test-download")); Path testRepoName = Path.of("test-repos"); - writer.save(testDownload.resolve(testRepoName).resolve(Path.of(".git")).toFile(), "testText", true); + writer.writeTextToFile(testDownload.resolve(testRepoName).resolve(Path.of(".git")).toFile(), "testText", true); - String json = TestFileReader.loadTextFile(new File("./src/test/resources/sechub_remote_data_config_source_code_scan_example.json")); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/sechub_remote_data_config_source_code_scan_example.json")); PrepareWrapperContext context = mock(PrepareWrapperContext.class); PrepareWrapperEnvironment environment = mock(PrepareWrapperEnvironment.class); diff --git a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperFileUploadServiceTest.java b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperFileUploadServiceTest.java index a264f83243..36aa3b9953 100644 --- a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperFileUploadServiceTest.java +++ b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperFileUploadServiceTest.java @@ -113,7 +113,7 @@ void uploadFile_throws_exception_when_checkSum_is_null() throws IOException { File tempDir = Files.createTempDirectory("test-sechub_archive-creator").toFile(); tempDir.deleteOnExit(); File file = new File(tempDir + "testfile.tar"); - writer.save(file, "testText", true); + writer.writeTextToFile(file, "testText", true); /* execute */ IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { @@ -134,7 +134,7 @@ void uploadFile_successfully_uploads_binary_file() throws IOException { File tempDir = Files.createTempDirectory("test-sechub_archive-creator").toFile(); tempDir.deleteOnExit(); File file = new File(tempDir + "testfile.tar"); - writer.save(file, "someExampleText", true); + writer.writeTextToFile(file, "someExampleText", true); /* execute */ uploadServiceToTest.uploadFile(projectId, jobUUID, file, checkSum); @@ -156,7 +156,7 @@ void uploadFile_successfully_uploads_source_zip_file() throws IOException { File tempDir = Files.createTempDirectory("test-sechub_archive-creator").toFile(); tempDir.deleteOnExit(); File file = new File(tempDir + "testfile.zip"); - writer.save(file, "someExampleText", true); + writer.writeTextToFile(file, "someExampleText", true); /* execute */ uploadServiceToTest.uploadFile(projectId, jobUUID, file, checkSum); diff --git a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperSechubConfigurationSupportTest.java b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperSechubConfigurationSupportTest.java index 7989b4ba9d..6346254028 100644 --- a/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperSechubConfigurationSupportTest.java +++ b/sechub-wrapper-prepare/src/test/java/com/mercedesbenz/sechub/wrapper/prepare/upload/PrepareWrapperSechubConfigurationSupportTest.java @@ -108,7 +108,7 @@ void replaceRemoteDataWithFilesystem_returns_SecHubConfigurationModel_when_SecHu @Test void replaceRemoteDataWithFilesystem_returns_SecHubConfigurationModel_when_SecHubConfigurationModel_data_binaries_is_not_empty() throws IOException { /* prepare */ - writer.save(testDownload.resolve(testTarFilename).toFile(), "testText", true); + writer.writeTextToFile(testDownload.resolve(testTarFilename).toFile(), "testText", true); SecHubConfigurationModel model = loadModelFromTestFile("sechub_remote_data_config_binary_code_scan_example.json"); @@ -136,7 +136,7 @@ void replaceRemoteDataWithFilesystem_returns_SecHubConfigurationModel_when_SecHu @Test void replaceRemoteDataWithFilesystem_returns_SecHubConfigurationModel_when_SecHubConfigurationModel_data_sources_is_not_empty() throws IOException { /* prepare */ - writer.save(testDownload.resolve(testRepoName).resolve(Path.of(".git")).toFile(), "testText", true); + writer.writeTextToFile(testDownload.resolve(testRepoName).resolve(Path.of(".git")).toFile(), "testText", true); SecHubConfigurationModel model = loadModelFromTestFile("sechub_remote_data_config_source_code_scan_example.json"); @@ -164,7 +164,7 @@ void replaceRemoteDataWithFilesystem_returns_SecHubConfigurationModel_when_SecHu @Test void replaceRemoteDataWithFilesystem_returns_expected_SecHubConfigurationModel_for_remote_binaries() throws IOException { /* prepare */ - writer.save(testDownload.resolve(testTarFilename).toFile(), "testText", true); + writer.writeTextToFile(testDownload.resolve(testTarFilename).toFile(), "testText", true); SecHubConfigurationModel model = loadModelFromTestFile("sechub_remote_data_config_binary_code_scan_example.json"); @@ -194,7 +194,7 @@ void replaceRemoteDataWithFilesystem_returns_expected_SecHubConfigurationModel_f @Test void replaceRemoteDataWithFilesystem_returns_expected_SecHubConfigurationModel_for_remote_sources() throws IOException { /* prepare */ - writer.save(testDownload.resolve(testRepoName).resolve(Path.of(".git")).toFile(), "testText", true); + writer.writeTextToFile(testDownload.resolve(testRepoName).resolve(Path.of(".git")).toFile(), "testText", true); SecHubConfigurationModel model = loadModelFromTestFile("sechub_remote_data_config_source_code_scan_example.json"); when(prepareContext.getSecHubConfiguration()).thenReturn(model); @@ -221,7 +221,7 @@ void replaceRemoteDataWithFilesystem_returns_expected_SecHubConfigurationModel_f } private SecHubConfigurationModel loadModelFromTestFile(String fileName) { - String json = TestFileReader.loadTextFile(new File("./src/test/resources/" + fileName)); + String json = TestFileReader.readTextFromFile(new File("./src/test/resources/" + fileName)); return createFromJSON(json); } } \ No newline at end of file diff --git a/sechub-wrapper-secret-validator/README.adoc b/sechub-wrapper-secret-validator/README.adoc new file mode 100644 index 0000000000..e4876885f3 --- /dev/null +++ b/sechub-wrapper-secret-validator/README.adoc @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +== Secret valdiation Wrapper + +This wrapper is a Spring Boot application, that is supposed to run on PDS solutions performing secret scans. +After the secret scanning tool is done it will try to validate secrets found and enhance the SARIF report with a custom PropertyBag for each finding location. +It will add a custom severity with the key `secretscan.sereco.severity` with +one of the following values `["info", "unclassified", "low", "medium", "high", "critical"]` to a SARIF PropertyBag which contains a generic map for custom values. +This values can then be used by the `sechub-sereco` module to improve the severity categorization of a finding. We add this custom severities with a `PropertyBag` because the SARIF default called `Level` does not support `CRITICAL`. + +In case the secret was valid,the key `secretscan.validated.by.url` will be added to the PropertyBag as well with the value containing the URL the secret was valid for. + +=== Usage + +==== Start wrapper +The wrapper application is an executable jar and needs no command line arguments. +It can automatically use all +https://mercedes-benz.github.io/sechub/latest/sechub-product-delegation-server.html#launcher-scripts[standard PDS environment variables] +and following special mandatory environment variable: + +---- +SECRET_VALIDATOR_CONFIGFILE +---- + +[IMPORTANT] +==== +The file, which must be configured by `SECRET_VALIDATOR_CONFIGFILE`, contains the configuration for each secret type, which basically tells the wrapper application how to validate secrets of a certain type. +For each product a custom file is necessary, where some kind of finding identifier should be used, like the SARIF `ruleId` to identify each configuration. +From the https://mercedes-benz.github.io/sechub/latest/sechub-product-delegation-server.html#launcher-scripts[standard PDS environment variables] +only the `PDS_JOB_RESULT_FILE` is mandatory because without a valid SARIF report from a secret scanning tool no validations can be done. +==== + +===== Configuration file example +```json +{ + "validatorConfigList" : [ { <1> + "ruleId" : "sarif-result-rule-id", <2> + "categorization" : { <3> + "defaultSeverity" : "high", + "validationFailedSeverity" : "medium", + "validationSuccessSeverity" : "critical" + }, + "requests" : [ { <4> + "proxyRequired" : true, + "url" : "https://api.example.com", + "headers" : [ { + "name" : "Authorization", + "valuePrefix" : "Bearer" + } ], + "expectedResponse" : { <5> + "httpStatus" : 200, + "contains" : { + "allOf" : [ "is", "there" ], + "oneOf" : [ "success", "authorized" ] + } + } + } ] + } ] +} +``` +<1> Define a list of configuration entries. One entry represents a type of secret identified by a dedicated rule id. +<2> The `ruleId` identifies this configuration so the requests defined in the later section are performed for each finding of this type. +<3> The `categorization` configuration. If empty no categorization will be performed, otherwise: +- The `defaultSeverity` will be applied if there are no requests defined for this type of secret. +- The `validationFailedSeverity` will be applied if all validation requests failed for this secret. +- The `validationSuccessSeverity` will be applied if the secret was successfully validated with one of the requests defined. +<4> The `requests` array defines all known requests to validate a secret of this certain type. +This is an array because for some secrets it might be necessary to validate against multiple servers e.g. when a company uses multiple AWS instances. +It might be necessary to perform validation requests to all possible AWS instances to check if a secret is valid for any of them: +- `proxyRequired` specifies if the URL can only be accessed using a proxy server. +- `url` specifies the URL the request will be performed to. +- `headers` specifies the header `name` and `valuePrefix` if necessary for the secret found. +It is an array because it could be useful in the future. Currently the header is used for the secret because it is assumed to be a token send via HTTP header. +<5> The `expectedResponse` section contains information expected on a request with a valid secret, +like a specific `httpStatus` code or the response body should contain `allOf` or `oneOf` certain strings. + + + +There exists also some optional variables: + +---- +SECRET_VALIDATOR_TRUSTALLCERTIFICATES +---- + +[TIP] +==== +When `SECRET_VALIDATOR_TRUSTALLCERTIFICATES` set to `true`, certificate errors on validation web requests will be ignored. +This can be useful, in certain situations or setups. +==== + +===== Proxy configuration +Inside the configuration file each request has to configure if a proxy server is required to access this specific URL or not. +The wrapper is implemented to use the default proxy of the system properties. To configure a proxy, launch the application with the following arguments. +For each request it will then be decided if the proxy is needed or not according to the configuration file provided: + +---- +java -Dhttp.proxyHost=localhost -Dhttp.proxyPort=1234 -Dhttps.proxyHost=localhost -Dhttps.proxyPort=1234 -jar secret-validation-wrapper.jar +---- + +==== Testing the wrapper +For testing purposes there is another implementation present which is only available and used if the wrapper is started with the `integrationtest` profile: +---- +java -Dspring.profiles.active=integrationtest -jar secret-validation-wrapper.jar +---- +If the `integrationtest` profile is used the application will flag all findings in the SARIF report as valid, that has a none empty list of requests configured. +In case no requests are configured the finding will be flagged as invalid. \ No newline at end of file diff --git a/sechub-wrapper-secret-validator/build.gradle b/sechub-wrapper-secret-validator/build.gradle new file mode 100644 index 0000000000..dc4bd23da1 --- /dev/null +++ b/sechub-wrapper-secret-validator/build.gradle @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +/*============================================================================ +* Build file for subproject +* +* Root build file: "${rootProject.projectDir}/build.gradle" +* ============================================================================ +*/ +dependencies { + /* runtime */ + implementation project(':sechub-commons-pds') + + implementation library.sarif_210 + + /* test */ + testImplementation project(':sechub-testframework') + + testImplementation spring_boot_dependency.junit_jupiter + testImplementation spring_boot_dependency.junit_jupiter_params + testImplementation spring_boot_dependency.mockito_core +} + +// TODO Jan 2024-07-17: version = versionData.getSecretValidatorWrapperVersion() + +compileJava { + + options.compilerArgs += ['-parameters'] + +} + +bootJar { + + doLast { + /* Here we copy the created wrapper jar as "sechub-wrapper-secret-validator.jar" into the + * pds-tools folder inside the sechub-integrationtest gradle subproject. + * + * This enables integration test script to use the solution secret-validator.sh script + * and just set as tool folder: ${project.buildDir}/pds-tools + */ + File integratonTestLaunchFile = new File("${rootProject.projectDir}/sechub-integrationtest/build/pds-tools/sechub-wrapper-secret-validator.jar") + + if (integratonTestLaunchFile.exists()){ + java.nio.file.Files.delete(integratonTestLaunchFile.toPath()) + }else{ + integratonTestLaunchFile.getParentFile().mkdirs(); + } + + File buildFile = new File("${project.buildDir}/libs/sechub-wrapper-secret-validator-${project.version}.jar") + java.nio.file.Files.copy(buildFile.toPath(),integratonTestLaunchFile.toPath()) + } + +} \ No newline at end of file diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/SecretValidatorApplication.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/SecretValidatorApplication.java new file mode 100644 index 0000000000..7cd8f00f64 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/SecretValidatorApplication.java @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SecretValidatorApplication { + + public static void main(String[] args) { + SpringApplication.run(SecretValidatorApplication.class, args); + } +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/cli/SecretValidatorCLI.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/cli/SecretValidatorCLI.java new file mode 100644 index 0000000000..fd7e18170b --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/cli/SecretValidatorCLI.java @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.cli; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.TextFileWriter; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.wrapper.secret.validator.execution.SecretValidatorExecutionService; +import com.mercedesbenz.sechub.wrapper.secret.validator.properties.SecretValidatorPDSJobResult; + +import de.jcup.sarif_2_1_0.model.SarifSchema210; + +@Profile("!test") +@Component +public class SecretValidatorCLI implements CommandLineRunner { + + private static final Logger LOG = LoggerFactory.getLogger(SecretValidatorCLI.class); + + @Autowired + SecretValidatorExecutionService executionService; + + @Autowired + SecretValidatorPDSJobResult pdsJobResult; + + @Override + public void run(String... args) throws Exception { + LOG.info("Secret validator starting"); + TextFileWriter fileWriter = new TextFileWriter(); + try { + SarifSchema210 report = executionService.execute(); + + String json = JSONConverter.get().toJSON(report, true); + fileWriter.writeTextToFile(pdsJobResult.getFile(), json, true); + } catch (Exception e) { + LOG.error("Execution failed!", e); + System.exit(1); + } + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/ResponseValidationService.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/ResponseValidationService.java new file mode 100644 index 0000000000..a2eb3a71a4 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/ResponseValidationService.java @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import java.net.http.HttpResponse; + +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorResponse; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorResponseContains; + +@Service +public class ResponseValidationService { + + public boolean isValidResponse(HttpResponse response, SecretValidatorResponse secretValidatorResponse) { + if (response == null || secretValidatorResponse == null) { + return false; + } + int expectedHttpStatus = secretValidatorResponse.getHttpStatus(); + SecretValidatorResponseContains containsSection = secretValidatorResponse.getContains(); + + // if the expected status is not set and no contains section is set, the secret + // cannot be validated + if (expectedHttpStatus <= 0 && hasNoContainsSection(containsSection)) { + return false; + } + + // no contains section set, so we use the status code + if (hasNoContainsSection(containsSection)) { + return expectedHttpStatus == response.statusCode(); + } + + // contains section and expected status code set, both must be valid + if (expectedHttpStatus > 0) { + boolean valid = expectedHttpStatus == response.statusCode(); + return valid && containsExpectedSnippets(containsSection, response.body()); + } + + // fallback if only a contains section was configured + return containsExpectedSnippets(containsSection, response.body()); + } + + private boolean hasNoContainsSection(SecretValidatorResponseContains contains) { + if (contains == null) { + return true; + } + return contains.getAllOf().isEmpty() && contains.getOneOf().isEmpty(); + } + + private boolean containsExpectedSnippets(SecretValidatorResponseContains contains, String body) { + // body and contains section should be specified at this point + if (body == null || contains == null) { + return false; + } + + for (String substring : contains.getAllOf()) { + // all must be present + if (!body.contains(substring)) { + return false; + } + } + if (contains.getOneOf().isEmpty()) { + return true; + } + + for (String substring : contains.getOneOf()) { + // if one is present it is enough at this point + if (body.contains(substring)) { + return true; + } + } + // this should not be reached + return false; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationResult.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationResult.java new file mode 100644 index 0000000000..87aa0fa3d7 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationResult.java @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +public class SecretValidationResult { + + private SecretValidationStatus validationStatus = SecretValidationStatus.NO_VALIDATION_CONFIGURED; + private String validatedByUrl; + + /** + * Get the status after validation of the current validated finding + * + * @return the validation status, never null + */ + public SecretValidationStatus getValidationStatus() { + return validationStatus; + } + + public void setValidationStatus(SecretValidationStatus validationStatus) { + if (validationStatus != null) { + this.validationStatus = validationStatus; + } + } + + public String getValidatedByUrl() { + return validatedByUrl; + } + + public void setValidatedByUrl(String validatedByUrl) { + this.validatedByUrl = validatedByUrl; + } +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationService.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationService.java new file mode 100644 index 0000000000..81d07ed578 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationService.java @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import java.util.List; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequest; + +import de.jcup.sarif_2_1_0.model.Region; + +public interface SecretValidationService { + + SecretValidationResult validateFindingByRegion(Region findingRegion, List requests, boolean trustAllCertificates); + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationServiceImpl.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationServiceImpl.java new file mode 100644 index 0000000000..e38f6d6f7f --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationServiceImpl.java @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequest; + +import de.jcup.sarif_2_1_0.model.ArtifactContent; +import de.jcup.sarif_2_1_0.model.Region; + +@Profile("!" + SecretValidatorSpringProfiles.INTEGRATIONTEST) +@Service +public class SecretValidationServiceImpl implements SecretValidationService { + + private static final Logger LOG = LoggerFactory.getLogger(SecretValidationServiceImpl.class); + + @Autowired + SecretValidatorWebRequestService webRequestService; + + @Override + public SecretValidationResult validateFindingByRegion(Region findingRegion, List requests, boolean trustAllCertificates) { + ArtifactContent snippet = findingRegion.getSnippet(); + SecretValidationResult validationResult = new SecretValidationResult(); + if (snippet == null) { + LOG.warn("Cannot validate finding because the SARIF snippet is null."); + validationResult.setValidationStatus(SecretValidationStatus.SARIF_SNIPPET_NOT_SET); + return validationResult; + } + + String snippetText = snippet.getText(); + if (snippetText == null || snippetText.isBlank()) { + LOG.warn("Cannot validate finding because the SARIF snippet text is null or empty."); + validationResult.setValidationStatus(SecretValidationStatus.SARIF_SNIPPET_NOT_SET); + return validationResult; + } + return webRequestService.validateFinding(snippetText, requests, trustAllCertificates); + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationStatus.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationStatus.java new file mode 100644 index 0000000000..d7a2538292 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationStatus.java @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +public enum SecretValidationStatus { + + VALID("The secret was successfully validated via a configured URL!"), + + INVALID("The secret could not be validated via any configured URL!"), + + NO_VALIDATION_CONFIGURED("No validation URLs are configured for this type of secret!"), + + ALL_VALIDATION_REQUESTS_FAILED("All request to validate a secret failed, due to network or connection errors!"), + + SARIF_SNIPPET_NOT_SET("SARIF finding does not contain a valid snippet to validate!"),; + + private String description; + + private SecretValidationStatus(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContext.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContext.java new file mode 100644 index 0000000000..a2c6a4589a --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContext.java @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorConfigurationModel; + +import de.jcup.sarif_2_1_0.model.SarifSchema210; + +public class SecretValidatorExecutionContext { + + private SarifSchema210 sarifReport; + + private Map validatorConfiguration = new HashMap<>(); + + private boolean trustAllCertificates; + + private SecretValidatorExecutionContext() { + } + + public SarifSchema210 getSarifReport() { + return sarifReport; + } + + public Map getValidatorConfiguration() { + return Collections.unmodifiableMap(validatorConfiguration); + } + + public boolean isTrustAllCertificates() { + return trustAllCertificates; + } + + public static SecretValidatorExecutionContextBuilder builder() { + return new SecretValidatorExecutionContextBuilder(); + } + + public static class SecretValidatorExecutionContextBuilder { + + private SarifSchema210 sarifReport; + + private Map validatorConfiguration = new HashMap<>(); + + private boolean trustAllCertificates; + + public SecretValidatorExecutionContextBuilder setSarifReport(SarifSchema210 report) { + this.sarifReport = report; + return this; + } + + public SecretValidatorExecutionContextBuilder setValidatorConfiguration(Map validatorConfiguration) { + this.validatorConfiguration = validatorConfiguration; + return this; + } + + public SecretValidatorExecutionContextBuilder setTrustAllCertificates(boolean trustAllCertificates) { + this.trustAllCertificates = trustAllCertificates; + return this; + } + + public SecretValidatorExecutionContext build() { + SecretValidatorExecutionContext secretValidatorExecutionContext = new SecretValidatorExecutionContext(); + secretValidatorExecutionContext.sarifReport = this.sarifReport; + secretValidatorExecutionContext.validatorConfiguration = this.validatorConfiguration; + secretValidatorExecutionContext.trustAllCertificates = this.trustAllCertificates; + + return secretValidatorExecutionContext; + } + + } +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContextFactory.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContextFactory.java new file mode 100644 index 0000000000..03ca70bc14 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContextFactory.java @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.TextFileReader; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorConfigurationModel; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorConfigurationModelList; +import com.mercedesbenz.sechub.wrapper.secret.validator.properties.SecretValidatorPDSJobResult; +import com.mercedesbenz.sechub.wrapper.secret.validator.properties.SecretValidatorProperties; + +import de.jcup.sarif_2_1_0.model.SarifSchema210; + +@Component +public class SecretValidatorExecutionContextFactory { + private static final Logger LOG = LoggerFactory.getLogger(SecretValidatorExecutionContextFactory.class); + + @Autowired + SecretValidatorPDSJobResult pdsResult; + + @Autowired + SecretValidatorProperties properties; + + TextFileReader reader = new TextFileReader(); + + public SecretValidatorExecutionContext create() { + LOG.info("Loading SARIF model from secret scan."); + SarifSchema210 report = createSarifReport(pdsResult.getFile()); + + LOG.info("Loading validator configuration for this pds solution."); + Map ruleConfigurations = createRuleConfigurations(properties.getConfigFile()); + + /* @formatter:off */ + SecretValidatorExecutionContext context = + SecretValidatorExecutionContext.builder() + .setSarifReport(report) + .setValidatorConfiguration(ruleConfigurations) + .setTrustAllCertificates(properties.isTrustAllCertificates()) + .build(); + /* @formatter:on */ + return context; + } + + private SarifSchema210 createSarifReport(File pdsResultFile) { + if (!pdsResultFile.exists()) { + throw new IllegalStateException("PDS job result file: " + pdsResultFile + " does not exist!"); + } else if (!pdsResultFile.canRead()) { + throw new IllegalStateException("PDS job result file: " + pdsResultFile + " is not readable!"); + } + + try { + String sarifReportJson = reader.readTextFromFile(pdsResultFile); + return JSONConverter.get().fromJSON(SarifSchema210.class, sarifReportJson); + } catch (Exception e) { + throw new IllegalStateException("Creating SARIF report model from: " + pdsResultFile + " failed!", e); + } + } + + private Map createRuleConfigurations(File validatorConfigFile) { + if (!validatorConfigFile.exists()) { + throw new IllegalStateException("Secret validator configuration file: " + validatorConfigFile + " does not exist!"); + } else if (!validatorConfigFile.canRead()) { + throw new IllegalStateException("Secret validator configuration file: " + validatorConfigFile + " is not readable!"); + } + + try { + String validatorConfigJson = reader.readTextFromFile(validatorConfigFile); + SecretValidatorConfigurationModelList configurationDataList = JSONConverter.get().fromJSON(SecretValidatorConfigurationModelList.class, + validatorConfigJson); + + Map ruleConfigurations = new HashMap<>(); + for (SecretValidatorConfigurationModel configData : configurationDataList.getValidatorConfigList()) { + ruleConfigurations.put(configData.getRuleId(), configData); + } + return ruleConfigurations; + } catch (Exception e) { + throw new IllegalStateException("Creating secret validator configuration from: " + validatorConfigFile + " failed!", e); + } + + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionService.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionService.java new file mode 100644 index 0000000000..6ff9d4b38b --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionService.java @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorCategorization; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorConfigurationModel; +import com.mercedesbenz.sechub.wrapper.secret.validator.support.SarifValidationSupport; + +import de.jcup.sarif_2_1_0.model.Location; +import de.jcup.sarif_2_1_0.model.Region; +import de.jcup.sarif_2_1_0.model.Result; +import de.jcup.sarif_2_1_0.model.Run; +import de.jcup.sarif_2_1_0.model.SarifSchema210; + +@Service +public class SecretValidatorExecutionService { + private static final Logger LOG = LoggerFactory.getLogger(SecretValidatorExecutionService.class); + + @Autowired + SecretValidatorExecutionContextFactory contextFactory; + + @Autowired + SecretValidationService validationService; + + @Autowired + SerecoSeveritySarifEnhancementService sarifEnhancementService; + + @Autowired + SarifValidationSupport sarifValidationSupport; + + public SarifSchema210 execute() { + SecretValidatorExecutionContext executionContext = contextFactory.create(); + Map validatorConfiguration = executionContext.getValidatorConfiguration(); + + // generally for secret scans it is only one run + List runs = executionContext.getSarifReport().getRuns(); + for (Run run : runs) { + List findings = run.getResults(); + for (Result finding : findings) { + SecretValidatorConfigurationModel config = validatorConfiguration.get(finding.getRuleId()); + if (isValidationPossible(config, finding)) { + validateFindingAndEnhanceSarif(executionContext, config, finding); + } + } + } + return executionContext.getSarifReport(); + } + + private boolean isValidationPossible(SecretValidatorConfigurationModel config, Result finding) { + if (!sarifValidationSupport.findingCanBeValidated(finding)) { + return false; + } + if (config == null) { + LOG.info("No config found to validate findings of rule: {}", finding.getRuleId()); + return false; + } + SecretValidatorCategorization categorization = config.getCategorization(); + if (categorization == null || categorization.isEmpty()) { + LOG.info("No config found to categorize findings of rule: {}", finding.getRuleId()); + return false; + } + return true; + } + + private void validateFindingAndEnhanceSarif(SecretValidatorExecutionContext executionContext, SecretValidatorConfigurationModel config, Result finding) { + for (Location location : finding.getLocations()) { + if (!sarifValidationSupport.findingLocationCanBeValidated(location)) { + continue; + } + Region findingRegion = location.getPhysicalLocation().getRegion(); + SecretValidationResult validationResult = validationService.validateFindingByRegion(findingRegion, config.getRequests(), + executionContext.isTrustAllCertificates()); + sarifEnhancementService.addSerecoSeverityInfo(validationResult, findingRegion, config.getCategorization()); + } + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorSpringProfiles.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorSpringProfiles.java new file mode 100644 index 0000000000..6e400c15a6 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorSpringProfiles.java @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +public class SecretValidatorSpringProfiles { + + public static final String INTEGRATIONTEST = "integrationtest"; + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorWebRequestService.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorWebRequestService.java new file mode 100644 index 0000000000..4dbcf2d04a --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorWebRequestService.java @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequest; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequestHeader; +import com.mercedesbenz.sechub.wrapper.secret.validator.support.SecretValidatorHttpClientFactory; + +@Service +public class SecretValidatorWebRequestService { + + private static final Logger LOG = LoggerFactory.getLogger(SecretValidatorWebRequestService.class); + + @Autowired + SecretValidatorHttpClientFactory httpClientFactory; + + @Autowired + ResponseValidationService responseValidationService; + + public SecretValidationResult validateFinding(String snippetText, List requests, boolean trustAllCertificates) { + SecretValidationResult validationResult = assertValidParams(snippetText, requests); + if (validationResult != null) { + return validationResult; + } + + HttpClient proxyHttpClient = httpClientFactory.createProxyHttpClient(trustAllCertificates); + HttpClient directHttpClient = httpClientFactory.createDirectHttpClient(trustAllCertificates); + HttpResponse response = null; + for (SecretValidatorRequest request : requests) { + + if (isRequestValid(request)) { + response = createAndExecuteHttpRequest(snippetText, proxyHttpClient, directHttpClient, request); + + if (responseValidationService.isValidResponse(response, request.getExpectedResponse())) { + LOG.info("Finding is valid!"); + return createValidationResult(SecretValidationStatus.VALID, request.getUrl()); + } + } + } + if (response == null) { + return createValidationResult(SecretValidationStatus.ALL_VALIDATION_REQUESTS_FAILED); + } + return createValidationResult(SecretValidationStatus.INVALID); + } + + private SecretValidationResult assertValidParams(String snippetText, List requests) { + if (snippetText == null || snippetText.isBlank()) { + LOG.warn("Cannot validate finding because the SARIF snippet text is null or empty."); + return createValidationResult(SecretValidationStatus.SARIF_SNIPPET_NOT_SET); + } + + if (requests.isEmpty()) { + LOG.info("Configured requests for this finding empty! Finding cannot be validated!"); + return createValidationResult(SecretValidationStatus.NO_VALIDATION_CONFIGURED); + } + return null; + } + + private boolean isRequestValid(SecretValidatorRequest request) { + if (request == null) { + LOG.info("Request config is null! Entry will be skipped."); + return false; + } + if (request.getUrl() == null) { + LOG.info("Request config URL is null! Entry will be skipped."); + return false; + } + return true; + } + + private HttpResponse createAndExecuteHttpRequest(String snippetText, HttpClient proxyHttpClient, HttpClient directHttpClient, + SecretValidatorRequest request) { + HttpResponse response = null; + try { + HttpRequest httpRequest = createHttpRequest(snippetText, request); + if (request.isProxyRequired()) { + response = proxyHttpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + } else { + response = directHttpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + } + } catch (IOException | InterruptedException e) { + LOG.error("Performing validation request failed!", e); + } + return response; + } + + private HttpRequest createHttpRequest(String snippetText, SecretValidatorRequest request) { + List headers = new ArrayList<>(); + for (SecretValidatorRequestHeader header : request.getHeaders()) { + String value = snippetText; + if (header.getValuePrefix() != null) { + value = header.getValuePrefix() + " " + snippetText; + } + headers.add(header.getName()); + headers.add(value); + } + String[] headersAsArrays = new String[headers.size()]; + try { + /* @formatter:off */ + return HttpRequest.newBuilder() + .headers(headers.toArray(headersAsArrays)) + .uri(request.getUrl().toURI()) + .GET() + .build(); + /* @formatter:on */ + } catch (URISyntaxException e) { + LOG.error("Request URL {} is invalid!", request.getUrl()); + throw new IllegalArgumentException(e); + } + } + + private SecretValidationResult createValidationResult(SecretValidationStatus validationStatus) { + return createValidationResult(validationStatus, null); + } + + private SecretValidationResult createValidationResult(SecretValidationStatus validationStatus, URL secretWasValidFor) { + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(validationStatus); + if (secretWasValidFor != null) { + validationResult.setValidatedByUrl(secretWasValidFor.toString()); + } + return validationResult; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SerecoSeveritySarifEnhancementService.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SerecoSeveritySarifEnhancementService.java new file mode 100644 index 0000000000..b7415b8fc6 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SerecoSeveritySarifEnhancementService.java @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static com.mercedesbenz.sechub.wrapper.secret.validator.support.SarifImporterKeys.*; + +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorCategorization; + +import de.jcup.sarif_2_1_0.model.PropertyBag; +import de.jcup.sarif_2_1_0.model.Region; + +@Service +public class SerecoSeveritySarifEnhancementService { + + public void addSerecoSeverityInfo(SecretValidationResult validationResult, Region findingRegion, SecretValidatorCategorization categorization) { + if (categorization == null || categorization.isEmpty()) { + return; + } + SecretValidationStatus validationStatus = validationResult.getValidationStatus(); + PropertyBag properties = new PropertyBag(); + + switch (validationStatus) { + case VALID: + properties.setAdditionalProperty(SECRETSCAN_SERECO_SEVERITY.getKey(), categorization.getValidationSuccessSeverity()); + String validatedByUrl = validationResult.getValidatedByUrl(); + if (validatedByUrl != null) { + properties.setAdditionalProperty(SECRETSCAN_VALIDATED_BY_URL.getKey(), validatedByUrl); + } + break; + case INVALID: + properties.setAdditionalProperty(SECRETSCAN_SERECO_SEVERITY.getKey(), categorization.getValidationFailedSeverity()); + break; + case NO_VALIDATION_CONFIGURED: + case SARIF_SNIPPET_NOT_SET: + case ALL_VALIDATION_REQUESTS_FAILED: + default: + properties.setAdditionalProperty(SECRETSCAN_SERECO_SEVERITY.getKey(), categorization.getDefaultSeverity()); + } + findingRegion.setProperties(properties); + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/test/IntegrationTestSecretValidationServiceImpl.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/test/IntegrationTestSecretValidationServiceImpl.java new file mode 100644 index 0000000000..2250eacfbc --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/test/IntegrationTestSecretValidationServiceImpl.java @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution.test; + +import java.util.List; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.wrapper.secret.validator.execution.SecretValidationResult; +import com.mercedesbenz.sechub.wrapper.secret.validator.execution.SecretValidationService; +import com.mercedesbenz.sechub.wrapper.secret.validator.execution.SecretValidationStatus; +import com.mercedesbenz.sechub.wrapper.secret.validator.execution.SecretValidatorSpringProfiles; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequest; + +import de.jcup.sarif_2_1_0.model.Region; + +/** + * This service is only available and used if the application is started with + * the {@link SecretValidatorSpringProfiles.INTEGRATIONTEST} profile. It returns + * findings as invalid if the list of requests passed to the + * validateFindingByRegion(...) method is null or + * empty. If the requests list contains at least one entry the finding is + * returned as valid. + * + */ +@Profile(SecretValidatorSpringProfiles.INTEGRATIONTEST) +@Service +public class IntegrationTestSecretValidationServiceImpl implements SecretValidationService { + + @Override + public SecretValidationResult validateFindingByRegion(Region findingRegion, List requests, boolean trustAllCertificates) { + if (requests == null || requests.isEmpty()) { + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(SecretValidationStatus.INVALID); + return validationResult; + } + + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(SecretValidationStatus.VALID); + return validationResult; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorCategorization.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorCategorization.java new file mode 100644 index 0000000000..988d6ed276 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorCategorization.java @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretValidatorCategorization { + + private String defaultSeverity; + private String validationFailedSeverity; + private String validationSuccessSeverity; + + public String getDefaultSeverity() { + return defaultSeverity; + } + + public void setDefaultSeverity(String defaultSeverity) { + this.defaultSeverity = defaultSeverity; + } + + public String getValidationFailedSeverity() { + return validationFailedSeverity; + } + + public void setValidationFailedSeverity(String validationFailedSeverity) { + this.validationFailedSeverity = validationFailedSeverity; + } + + public String getValidationSuccessSeverity() { + return validationSuccessSeverity; + } + + public void setValidationSuccessSeverity(String validationSuccessSeverity) { + this.validationSuccessSeverity = validationSuccessSeverity; + } + + /** + * Check if no severities were configured for. + * + * @return true, if all fields are null and therefore not + * configured and false, if any of the fields is set to a value other + * than null. + */ + @JsonIgnore + public boolean isEmpty() { + return defaultSeverity == null && validationFailedSeverity == null && validationSuccessSeverity == null; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModel.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModel.java new file mode 100644 index 0000000000..f941a25eff --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModel.java @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretValidatorConfigurationModel { + + private String ruleId; + private SecretValidatorCategorization categorization; + private List requests = new ArrayList<>(); + + public String getRuleId() { + return ruleId; + } + + public void setRuleId(String ruleId) { + this.ruleId = ruleId; + } + + public SecretValidatorCategorization getCategorization() { + return categorization; + } + + public void setCategorization(SecretValidatorCategorization categorization) { + this.categorization = categorization; + } + + public List getRequests() { + return Collections.unmodifiableList(requests); + } + + /** + * Every time this setter is called the list will be cleared, but it can never + * be null. In case the parameter is null the list, the list will stay empty, + * but never null. + * + * @param requests + */ + public void setRequests(List requests) { + this.requests.clear(); + if (requests == null) { + return; + } + this.requests.addAll(requests); + } +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModelList.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModelList.java new file mode 100644 index 0000000000..0a2185cccc --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModelList.java @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretValidatorConfigurationModelList { + + private List validatorConfigList = new ArrayList<>(); + + public List getValidatorConfigList() { + return Collections.unmodifiableList(validatorConfigList); + } + + /** + * Every time this setter is called the list will be cleared, but it can never + * be null. In case the parameter is null the list, the list will stay empty, + * but never null. + * + * @param validatorConfigList + */ + public void setValidatorConfigList(List validatorConfigList) { + this.validatorConfigList.clear(); + if (validatorConfigList == null) { + return; + } + this.validatorConfigList.addAll(validatorConfigList); + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorRequest.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorRequest.java new file mode 100644 index 0000000000..b5a6a837dd --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorRequest.java @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretValidatorRequest { + + private URL url; + private boolean proxyRequired; + private List headers = new ArrayList<>(); + private SecretValidatorResponse expectedResponse; + + public URL getUrl() { + return url; + } + + public void setUrl(URL url) { + this.url = url; + } + + public boolean isProxyRequired() { + return proxyRequired; + } + + public void setProxyRequired(boolean proxyRequired) { + this.proxyRequired = proxyRequired; + } + + public List getHeaders() { + return Collections.unmodifiableList(headers); + } + + /** + * Every time this setter is called the list will be cleared, but it can never + * be null. In case the parameter is null the list, the list will stay empty, + * but never null. + * + * @param headers + */ + public void setHeaders(List headers) { + this.headers.clear(); + if (headers == null) { + return; + } + this.headers.addAll(headers); + } + + public SecretValidatorResponse getExpectedResponse() { + return expectedResponse; + } + + public void setExpectedResponse(SecretValidatorResponse expectedResponse) { + this.expectedResponse = expectedResponse; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorRequestHeader.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorRequestHeader.java new file mode 100644 index 0000000000..5435419938 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorRequestHeader.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretValidatorRequestHeader { + + private String name; + private String valuePrefix; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValuePrefix() { + return valuePrefix; + } + + public void setValuePrefix(String valuePrefix) { + this.valuePrefix = valuePrefix; + } +} \ No newline at end of file diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorResponse.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorResponse.java new file mode 100644 index 0000000000..9f023235c4 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorResponse.java @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretValidatorResponse { + + private int httpStatus; + private SecretValidatorResponseContains contains; + + public int getHttpStatus() { + return httpStatus; + } + + public void setHttpStatus(int httpStatus) { + this.httpStatus = httpStatus; + } + + public SecretValidatorResponseContains getContains() { + return contains; + } + + public void setContains(SecretValidatorResponseContains contains) { + this.contains = contains; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorResponseContains.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorResponseContains.java new file mode 100644 index 0000000000..ad55ee71b3 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorResponseContains.java @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SecretValidatorResponseContains { + + private List allOf = new ArrayList<>(); + private List oneOf = new ArrayList<>(); + + public List getAllOf() { + return Collections.unmodifiableList(allOf); + } + + /** + * Every time this setter is called the list will be cleared, but it can never + * be null. In case the parameter is null the list, the list will stay empty, + * but never null. + * + * @param allOf + */ + public void setAllOf(List allOf) { + this.allOf.clear(); + if (allOf == null) { + return; + } + this.allOf.addAll(allOf); + } + + public List getOneOf() { + return Collections.unmodifiableList(oneOf); + } + + /** + * Every time this setter is called the list will be cleared, but it can never + * be null. In case the parameter is null the list, the list will stay empty, + * but never null. + * + * @param oneOf + */ + public void setOneOf(List oneOf) { + this.oneOf.clear(); + if (oneOf == null) { + return; + } + this.oneOf.addAll(oneOf); + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorConfiguration.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorConfiguration.java new file mode 100644 index 0000000000..12b793da20 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorConfiguration.java @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.properties; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({ SecretValidatorProperties.class, SecretValidatorPDSJobResult.class }) +public class SecretValidatorConfiguration { + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPDSJobResult.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPDSJobResult.java new file mode 100644 index 0000000000..6bc6e8497c --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPDSJobResult.java @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.properties; + +import java.io.File; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "pds.job.result") +public class SecretValidatorPDSJobResult { + + private final File file; + + @ConstructorBinding + public SecretValidatorPDSJobResult(File file) { + if (file == null) { + throw new IllegalArgumentException("The PDS result file is null!"); + } + + this.file = file; + + if (!this.file.exists()) { + throw new IllegalArgumentException("The PDS result file " + file + " does not exist!"); + } + if (!this.file.canRead()) { + throw new IllegalArgumentException("The PDS result file " + file + " is not readable!"); + } + } + + public File getFile() { + return file; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorProperties.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorProperties.java new file mode 100644 index 0000000000..7e3112eb88 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorProperties.java @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.properties; + +import java.io.File; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "secret.validator") +public class SecretValidatorProperties { + + private final File configFile; + private final boolean trustAllCertificates; + + @ConstructorBinding + public SecretValidatorProperties(File configFile, boolean trustAllCertificates) { + if (configFile == null) { + throw new IllegalArgumentException("The secret validator configuration file is null!"); + } + + this.configFile = configFile; + + if (!this.configFile.exists()) { + throw new IllegalArgumentException("The secret validator configuration file " + configFile + " does not exist!"); + } + if (!this.configFile.canRead()) { + throw new IllegalArgumentException("The secret validator configuration file " + configFile + " is not readable!"); + } + + this.trustAllCertificates = trustAllCertificates; + } + + public File getConfigFile() { + return configFile; + } + + public boolean isTrustAllCertificates() { + return trustAllCertificates; + } + +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifImporterKeys.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifImporterKeys.java new file mode 100644 index 0000000000..0e9c6caf49 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifImporterKeys.java @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.support; + +/* + * If this needs to get changed, make sure to change + * com.mercedesbenz.sechub.sereco.importer.SarifImporterKeys accordingly + */ +public enum SarifImporterKeys { + + SECRETSCAN_SERECO_SEVERITY("secretscan.sereco.severity", "The key for the sereco severity which is more precise than the SARIF Level enum."), + + SECRETSCAN_VALIDATED_BY_URL("secretscan.validated.by.url", "The key for the URL the secret was validated with."), + + ; + + private String key; + private String description; + + private SarifImporterKeys(String key, String description) { + this.key = key; + this.description = description; + } + + public String getKey() { + return key; + } + + public String getDescription() { + return description; + } +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifValidationSupport.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifValidationSupport.java new file mode 100644 index 0000000000..a55ed48903 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifValidationSupport.java @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.support; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import de.jcup.sarif_2_1_0.model.Location; +import de.jcup.sarif_2_1_0.model.PhysicalLocation; +import de.jcup.sarif_2_1_0.model.Region; +import de.jcup.sarif_2_1_0.model.Result; + +@Component +public class SarifValidationSupport { + + private static final Logger LOG = LoggerFactory.getLogger(SarifValidationSupport.class); + + public boolean findingCanBeValidated(Result finding) { + if (finding == null) { + LOG.info("Finding entry in SARIF report is null. Finding validation will be skipped."); + return false; + } + List locations = finding.getLocations(); + if (locations == null || locations.isEmpty()) { + LOG.info("Finding locations list in sarif report are null or empty. Finding validation will be skipped."); + return false; + } + return true; + } + + public boolean findingLocationCanBeValidated(Location location) { + if (location == null) { + LOG.info("Finding location entry in SARIF report is null. Finding validation will be skipped."); + return false; + } + PhysicalLocation physicalLocation = location.getPhysicalLocation(); + if (physicalLocation == null) { + LOG.info("Finding physical location entry in SARIF report is null. Finding validation will be skipped."); + return false; + } + Region findingRegion = physicalLocation.getRegion(); + if (findingRegion == null) { + LOG.info("Finding physical location region entry in SARIF report is null. Finding validation will be skipped."); + return false; + } + return true; + } +} diff --git a/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SecretValidatorHttpClientFactory.java b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SecretValidatorHttpClientFactory.java new file mode 100644 index 0000000000..7b1f5ac56f --- /dev/null +++ b/sechub-wrapper-secret-validator/src/main/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SecretValidatorHttpClientFactory.java @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.support; + +import java.net.ProxySelector; +import java.net.http.HttpClient; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.springframework.stereotype.Component; + +@Component +public class SecretValidatorHttpClientFactory { + private static final String TLS = "TLS"; + + public HttpClient createProxyHttpClient(boolean trustAllCertificates) { + if (trustAllCertificates) { + TrustManager pseudoTrustManager = createTrustManagerWhichTrustsEveryBody(); + SSLContext sslContext = createSSLContextForTrustManager(pseudoTrustManager); + /* @formatter:off */ + return HttpClient.newBuilder() + .proxy(ProxySelector.getDefault()) + .sslContext(sslContext) + .build(); + /* @formatter:on */ + } else { + /* @formatter:off */ + return HttpClient.newBuilder() + .proxy(ProxySelector.getDefault()) + .build(); + /* @formatter:on */ + } + } + + public HttpClient createDirectHttpClient(boolean trustAllCertificates) { + if (trustAllCertificates) { + TrustManager pseudoTrustManager = createTrustManagerWhichTrustsEveryBody(); + SSLContext sslContext = createSSLContextForTrustManager(pseudoTrustManager); + /* @formatter:off */ + return HttpClient.newBuilder() + .sslContext(sslContext) + .build(); + /* @formatter:on */ + } else { + return HttpClient.newBuilder().build(); + } + } + + private X509TrustManager createTrustManagerWhichTrustsEveryBody() { + return new X509TrustManager() { + + private X509Certificate[] emptyCertificatesArray = new X509Certificate[] {}; + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + /* we do not check the client - we trust all */ + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + /* we do not check the server - we trust all */ + } + + public X509Certificate[] getAcceptedIssuers() { + return emptyCertificatesArray; + } + }; + } + + private SSLContext createSSLContextForTrustManager(TrustManager trustManager) { + SSLContext sslContext = null; + try { + sslContext = SSLContext.getInstance(TLS); + sslContext.init(null, new TrustManager[] { trustManager }, null); + + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new IllegalStateException("Was not able to create trust all context", e); + } + + } +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/cli/SecretValidatorSpringBootTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/cli/SecretValidatorSpringBootTest.java new file mode 100644 index 0000000000..714048a605 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/cli/SecretValidatorSpringBootTest.java @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import com.mercedesbenz.sechub.wrapper.secret.validator.execution.SecretValidatorExecutionService; +import com.mercedesbenz.sechub.wrapper.secret.validator.support.SarifImporterKeys; + +import de.jcup.sarif_2_1_0.model.Location; +import de.jcup.sarif_2_1_0.model.Region; +import de.jcup.sarif_2_1_0.model.Result; +import de.jcup.sarif_2_1_0.model.Run; +import de.jcup.sarif_2_1_0.model.SarifSchema210; + +@SpringBootTest +@ExtendWith(SpringExtension.class) +@EnableConfigurationProperties +@TestPropertySource(properties = { "secret.validator.config-file=src/test/resources/config-test-files/valid-files/test-config.json", + "secret.validator.trust-all-certificates=false", "pds.job.result.file=src/test/resources/config-test-files/valid-files/test-result.txt" }) +@ActiveProfiles("test") +class SecretValidatorSpringBootTest { + + @Autowired + SecretValidatorExecutionService executionService; + + @Test + void execution_service_with_correct_configuration_without_validation_categorizes_findings_with_default_configured() { + /* execute */ + SarifSchema210 report = executionService.execute(); + + /* test */ + Run run = report.getRuns().get(0); + for (Result finding : run.getResults()) { + // since all validation requests fail the default categorization of the config + // file will be used which is high + assertFindingHasSerecoSeverity("high", finding); + } + } + + private void assertFindingHasSerecoSeverity(String expectedSerecoSeverity, Result finding) { + Location location = finding.getLocations().get(0); + Region region = location.getPhysicalLocation().getRegion(); + Map additionalProperties = region.getProperties().getAdditionalProperties(); + String serecoSeverity = (String) additionalProperties.get(SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey()); + + assertEquals(expectedSerecoSeverity, serecoSeverity); + + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/ResponseValidationServiceTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/ResponseValidationServiceTest.java new file mode 100644 index 0000000000..010ba4c668 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/ResponseValidationServiceTest.java @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorResponse; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorResponseContains; + +class ResponseValidationServiceTest { + + private ResponseValidationService serviceToTest = new ResponseValidationService(); + + @Test + void response_is_null_results_in_validation_is_false() { + /* execute */ + boolean isValid = serviceToTest.isValidResponse(null, new SecretValidatorResponse()); + + /* test */ + assertFalse(isValid); + } + + @Test + void validator_response_config_is_null_results_in_validation_is_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, null); + + /* test */ + assertFalse(isValid); + } + + @Test + void validator_response_config_is_not_configured_results_in_validation_is_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(0); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, new SecretValidatorResponse()); + + /* test */ + assertFalse(isValid); + } + + @ParameterizedTest + @ValueSource(ints = { 200, 302, 404, 500 }) + void validator_response_config_status_code_configured_and_contains_is_null_results_in_http_status_code_check(int responseCode) { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(responseCode); + SecretValidatorResponse secretValidatorResponse = new SecretValidatorResponse(); + secretValidatorResponse.setHttpStatus(302); + secretValidatorResponse.setContains(null); + + // with this configuration we expect the statuscodes being compared, + // since nothing else is configured inside the SecretValidatorResponse + boolean expectedResponse = response.statusCode() == secretValidatorResponse.getHttpStatus(); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, secretValidatorResponse); + + /* test */ + assertEquals(expectedResponse, isValid); + } + + @ParameterizedTest + @ValueSource(ints = { 200, 302, 404, 500 }) + void validator_response_config_status_code_configured_and_contains_is_empty_results_in_http_status_code_check(int responseCode) { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(responseCode); + SecretValidatorResponse secretValidatorResponse = new SecretValidatorResponse(); + secretValidatorResponse.setHttpStatus(302); + secretValidatorResponse.setContains(new SecretValidatorResponseContains()); + + // with this configuration we expect the satuscodes being compared, + // since nothing else is configured inside the SecretValidatorResponse + boolean expectedResponse = response.statusCode() == secretValidatorResponse.getHttpStatus(); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, secretValidatorResponse); + + /* test */ + assertEquals(expectedResponse, isValid); + } + + @Test + void validator_response_config_status_code_not_configured_and_contains_is_null_results_in_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + SecretValidatorResponse secretValidatorResponse = new SecretValidatorResponse(); + secretValidatorResponse.setContains(null); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, secretValidatorResponse); + + /* test */ + assertFalse(isValid); + } + + @Test + void validator_response_config_status_code_not_configured_and_contains_is_empty_results_in_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + SecretValidatorResponse secretValidatorResponse = new SecretValidatorResponse(); + secretValidatorResponse.setContains(new SecretValidatorResponseContains()); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, secretValidatorResponse); + + /* test */ + assertFalse(isValid); + } + + @Test + void response_body_is_null_with_statuscode_configured_results_in_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn(null); + SecretValidatorResponse responseConfig = createSecretValidatorResponseWithAllOfSetup(); + responseConfig.setHttpStatus(200); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, responseConfig); + + /* test */ + assertFalse(isValid); + } + + @Test + void response_body_is_null_without_statuscode_configured_results_in_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn(null); + SecretValidatorResponse responseConfig = createSecretValidatorResponseWithAllOfSetup(); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, responseConfig); + + /* test */ + assertFalse(isValid); + } + + @Test + void response_body_contains_all_of_expected_substings_returns_true() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn("OK is authorized"); + SecretValidatorResponse responseConfig = createSecretValidatorResponseWithAllOfSetup(); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, responseConfig); + + /* test */ + assertTrue(isValid); + } + + @Test + void response_body_does_not_contain_all_of_expected_substings_returns_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn("OK"); + SecretValidatorResponse responseConfig = createSecretValidatorResponseWithAllOfSetup(); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, responseConfig); + + /* test */ + assertFalse(isValid); + } + + @Test + void response_body_contains_one_of_expected_substings_returns_true() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn("OK"); + SecretValidatorResponse responseConfig = createSecretValidatorResponseWithOneOfSetup(); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, responseConfig); + + /* test */ + assertTrue(isValid); + } + + @Test + void response_body_does_not_contain_one_of_expected_substings_returns_false() { + /* prepare */ + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(200); + when(response.body()).thenReturn("Authentication successful!"); + SecretValidatorResponse responseConfig = createSecretValidatorResponseWithAllOfSetup(); + + /* execute */ + boolean isValid = serviceToTest.isValidResponse(response, responseConfig); + + /* test */ + assertFalse(isValid); + } + + private SecretValidatorResponse createSecretValidatorResponseWithAllOfSetup() { + SecretValidatorResponse secretValidatorResponse = new SecretValidatorResponse(); + SecretValidatorResponseContains contains = new SecretValidatorResponseContains(); + List allOf = new ArrayList<>(); + allOf.add("OK"); + allOf.add("authorized"); + + contains.setAllOf(allOf); + secretValidatorResponse.setContains(contains); + + return secretValidatorResponse; + } + + private SecretValidatorResponse createSecretValidatorResponseWithOneOfSetup() { + SecretValidatorResponse secretValidatorResponse = new SecretValidatorResponse(); + SecretValidatorResponseContains contains = new SecretValidatorResponseContains(); + List oneOf = new ArrayList<>(); + oneOf.add("OK"); + oneOf.add("authorized"); + + contains.setOneOf(oneOf); + secretValidatorResponse.setContains(contains); + + return secretValidatorResponse; + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationResultTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationResultTest.java new file mode 100644 index 0000000000..76a116ba27 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationResultTest.java @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class SecretValidationResultTest { + + @ParameterizedTest + @EnumSource(SecretValidationStatus.class) + void setting_a_new_value_results_in_the_specified_value_being_set(SecretValidationStatus status) { + /* prepare */ + SecretValidationResult validationResult = new SecretValidationResult(); + + /* execute */ + validationResult.setValidationStatus(status); + + /* test */ + assertEquals(status, validationResult.getValidationStatus()); + + } + + @Test + void setting_null_results_in_the_default_value_staying_set() { + /* prepare */ + SecretValidationResult validationResult = new SecretValidationResult(); + + /* execute */ + validationResult.setValidationStatus(null); + + /* test */ + assertEquals(SecretValidationStatus.NO_VALIDATION_CONFIGURED, validationResult.getValidationStatus()); + + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationServiceImplTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationServiceImplTest.java new file mode 100644 index 0000000000..1a1c2fd76b --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidationServiceImplTest.java @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequest; + +import de.jcup.sarif_2_1_0.model.ArtifactContent; +import de.jcup.sarif_2_1_0.model.Region; + +class SecretValidationServiceImplTest { + + private SecretValidationServiceImpl serviceToTest; + + private SecretValidatorWebRequestService webRequestService; + + @BeforeEach + void beforeEach() { + serviceToTest = new SecretValidationServiceImpl(); + + webRequestService = mock(SecretValidatorWebRequestService.class); + serviceToTest.webRequestService = webRequestService; + + } + + @Test + void region_snippet_is_null_returns_expected_validation_result() { + /* prepare */ + Region region = new Region(); + List requests = new ArrayList<>(); + when(webRequestService.validateFinding(null, requests, true)).thenReturn(new SecretValidationResult()); + + /* execute */ + SecretValidationResult validateFindingByRegion = serviceToTest.validateFindingByRegion(region, requests, true); + + /* test */ + verify(webRequestService, never()).validateFinding(null, requests, true); + assertEquals(SecretValidationStatus.SARIF_SNIPPET_NOT_SET, validateFindingByRegion.getValidationStatus()); + + } + + @Test + void region_snippet_text_is_null_returns_expected_validation_result() { + /* prepare */ + Region region = new Region(); + region.setSnippet(new ArtifactContent()); + List requests = new ArrayList<>(); + when(webRequestService.validateFinding(region.getSnippet().getText(), requests, true)).thenReturn(new SecretValidationResult()); + + /* execute */ + SecretValidationResult validateFindingByRegion = serviceToTest.validateFindingByRegion(region, requests, true); + + /* test */ + verify(webRequestService, never()).validateFinding(region.getSnippet().getText(), requests, true); + assertEquals(SecretValidationStatus.SARIF_SNIPPET_NOT_SET, validateFindingByRegion.getValidationStatus()); + + } + + @Test + void region_snippet_text_is_blank_returns_expected_validation_result() { + /* prepare */ + Region region = new Region(); + ArtifactContent snippet = new ArtifactContent(); + snippet.setText(" "); + region.setSnippet(snippet); + List requests = new ArrayList<>(); + when(webRequestService.validateFinding(region.getSnippet().getText(), requests, true)).thenReturn(new SecretValidationResult()); + + /* execute */ + SecretValidationResult validateFindingByRegion = serviceToTest.validateFindingByRegion(region, requests, true); + + /* test */ + verify(webRequestService, never()).validateFinding(region.getSnippet().getText(), requests, true); + assertEquals(SecretValidationStatus.SARIF_SNIPPET_NOT_SET, validateFindingByRegion.getValidationStatus()); + + } + + @Test + void region_snippet_text_is_set_results_in_web_request_service_called_once() { + /* prepare */ + Region region = new Region(); + ArtifactContent snippet = new ArtifactContent(); + snippet.setText("secret"); + region.setSnippet(snippet); + List requests = new ArrayList<>(); + when(webRequestService.validateFinding(region.getSnippet().getText(), requests, true)).thenReturn(new SecretValidationResult()); + + /* execute */ + SecretValidationResult validateFindingByRegion = serviceToTest.validateFindingByRegion(region, requests, true); + + /* test */ + verify(webRequestService, times(1)).validateFinding(region.getSnippet().getText(), requests, true); + assertEquals(SecretValidationStatus.NO_VALIDATION_CONFIGURED, validateFindingByRegion.getValidationStatus()); + + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContextFactoryTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContextFactoryTest.java new file mode 100644 index 0000000000..1e2bc60bda --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionContextFactoryTest.java @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.File; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.wrapper.secret.validator.properties.SecretValidatorPDSJobResult; +import com.mercedesbenz.sechub.wrapper.secret.validator.properties.SecretValidatorProperties; + +class SecretValidatorExecutionContextFactoryTest { + + private SecretValidatorExecutionContextFactory factoryToTest; + + private SecretValidatorPDSJobResult invalidsecretValidatorPDSJobResult; + private SecretValidatorPDSJobResult validSecretValidatorPDSJobResult; + private SecretValidatorProperties invalidProperties; + private SecretValidatorProperties validProperties; + + private static final File invalidSarifFile = new File("src/test/resources/config-test-files/invalid-files/invalid-sarif.txt"); + private static final File invalidConfigFile = new File("src/test/resources/config-test-files/invalid-files/invalid-validator-config.txt"); + private static final File validSarifFile = new File("src/test/resources/config-test-files/valid-files/test-result.txt"); + private static final File validConfigFile = new File("src/test/resources/config-test-files/valid-files/test-config.json"); + + @BeforeEach + void beforeEach() { + factoryToTest = new SecretValidatorExecutionContextFactory(); + + invalidsecretValidatorPDSJobResult = new SecretValidatorPDSJobResult(invalidSarifFile); + + invalidProperties = new SecretValidatorProperties(invalidConfigFile, false); + + validSecretValidatorPDSJobResult = new SecretValidatorPDSJobResult(validSarifFile); + + validProperties = new SecretValidatorProperties(validConfigFile, false); + } + + @Test + void invalid_sarif_pds_job_result_file_throws_exception() { + /* prepare */ + factoryToTest.pdsResult = invalidsecretValidatorPDSJobResult; + + /* execute + test */ + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> factoryToTest.create()); + assertEquals("Creating SARIF report model from: " + invalidsecretValidatorPDSJobResult.getFile() + " failed!", exception.getMessage()); + } + + @Test + void invalid_secret_validator_config_file_throws_exception() { + /* prepare */ + factoryToTest.pdsResult = validSecretValidatorPDSJobResult; + factoryToTest.properties = invalidProperties; + + /* execute + test */ + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> factoryToTest.create()); + assertEquals("Creating secret validator configuration from: " + invalidProperties.getConfigFile() + " failed!", exception.getMessage()); + } + + @Test + void valid_files_return_valid_execution_context() { + /* prepare */ + factoryToTest.pdsResult = validSecretValidatorPDSJobResult; + factoryToTest.properties = validProperties; + + /* execute */ + SecretValidatorExecutionContext secretValidatorExecutionContext = factoryToTest.create(); + + /* test */ + assertNotNull(secretValidatorExecutionContext); + assertNotNull(secretValidatorExecutionContext.getSarifReport()); + assertEquals(1, secretValidatorExecutionContext.getValidatorConfiguration().size()); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionServiceTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionServiceTest.java new file mode 100644 index 0000000000..11d9ed2184 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorExecutionServiceTest.java @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.test.TestFileReader; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorCategorization; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorConfigurationModel; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorConfigurationModelList; +import com.mercedesbenz.sechub.wrapper.secret.validator.support.SarifValidationSupport; + +import de.jcup.sarif_2_1_0.model.Location; +import de.jcup.sarif_2_1_0.model.Result; +import de.jcup.sarif_2_1_0.model.Run; +import de.jcup.sarif_2_1_0.model.SarifSchema210; + +class SecretValidatorExecutionServiceTest { + + private SecretValidatorExecutionService serviceToTest; + + private SecretValidatorExecutionContextFactory contextFactory; + private SecretValidationServiceImpl validationService; + private SerecoSeveritySarifEnhancementService sarifEnhancementService; + private SarifValidationSupport sarifValidationSupport; + + @BeforeEach + void beforeEach() { + serviceToTest = new SecretValidatorExecutionService(); + + contextFactory = mock(SecretValidatorExecutionContextFactory.class); + validationService = mock(SecretValidationServiceImpl.class); + sarifEnhancementService = mock(SerecoSeveritySarifEnhancementService.class); + sarifValidationSupport = mock(SarifValidationSupport.class); + + serviceToTest.contextFactory = contextFactory; + serviceToTest.validationService = validationService; + serviceToTest.sarifEnhancementService = sarifEnhancementService; + serviceToTest.sarifValidationSupport = sarifValidationSupport; + } + + @Test + void finding_cannot_be_validated_results_in_validation_and_categorization_never_being_called() { + SarifSchema210 report = createEmptySarifReport(); + + @SuppressWarnings("unchecked") + Map ruleConfigurations = mock(Map.class); + when(ruleConfigurations.get(any())).thenReturn(null); + + SecretValidatorExecutionContext executionContext = SecretValidatorExecutionContext.builder().setSarifReport(report) + .setValidatorConfiguration(ruleConfigurations).setTrustAllCertificates(true).build(); + when(contextFactory.create()).thenReturn(executionContext); + + when(sarifValidationSupport.findingCanBeValidated(any())).thenReturn(false); + + /* execute */ + serviceToTest.execute(); + + /* test */ + verify(contextFactory, times(1)).create(); + verify(validationService, never()).validateFindingByRegion(any(), any(), anyBoolean()); + verify(sarifEnhancementService, never()).addSerecoSeverityInfo(any(), any(), any()); + verify(sarifValidationSupport, times(1)).findingCanBeValidated(any()); + } + + @Test + void empty_config_map_results_in_validation_and_categorization_never_being_called() { + /* prepare */ + SarifSchema210 report = createEmptySarifReport(); + + @SuppressWarnings("unchecked") + Map ruleConfigurations = mock(Map.class); + when(ruleConfigurations.get(any())).thenReturn(null); + + SecretValidatorExecutionContext executionContext = SecretValidatorExecutionContext.builder().setSarifReport(report) + .setValidatorConfiguration(ruleConfigurations).setTrustAllCertificates(true).build(); + when(contextFactory.create()).thenReturn(executionContext); + + when(sarifValidationSupport.findingCanBeValidated(any())).thenReturn(true); + + /* execute */ + serviceToTest.execute(); + + /* test */ + verify(contextFactory, times(1)).create(); + verify(validationService, never()).validateFindingByRegion(any(), any(), anyBoolean()); + verify(sarifEnhancementService, never()).addSerecoSeverityInfo(any(), any(), any()); + verify(sarifValidationSupport, times(1)).findingCanBeValidated(any()); + } + + @Test + void categorization_of_config_is_null_results_in_validation_and_categorization_never_being_called() { + SarifSchema210 report = createEmptySarifReport(); + + SecretValidatorConfigurationModel config = new SecretValidatorConfigurationModel(); + config.setCategorization(null); + + @SuppressWarnings("unchecked") + Map ruleConfigurations = mock(Map.class); + when(ruleConfigurations.get(any())).thenReturn(config); + + SecretValidatorExecutionContext executionContext = SecretValidatorExecutionContext.builder().setSarifReport(report) + .setValidatorConfiguration(ruleConfigurations).setTrustAllCertificates(true).build(); + when(contextFactory.create()).thenReturn(executionContext); + + when(sarifValidationSupport.findingCanBeValidated(any())).thenReturn(true); + + /* execute */ + serviceToTest.execute(); + + /* test */ + verify(contextFactory, times(1)).create(); + verify(validationService, never()).validateFindingByRegion(any(), any(), anyBoolean()); + verify(sarifEnhancementService, never()).addSerecoSeverityInfo(any(), any(), any()); + verify(sarifValidationSupport, times(1)).findingCanBeValidated(any()); + } + + @Test + void categorization_of_config_is_empty_results_in_validation_and_categorization_never_being_called() { + SarifSchema210 report = createEmptySarifReport(); + + SecretValidatorConfigurationModel config = new SecretValidatorConfigurationModel(); + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + config.setCategorization(categorization); + + @SuppressWarnings("unchecked") + Map ruleConfigurations = mock(Map.class); + when(ruleConfigurations.get(any())).thenReturn(config); + + SecretValidatorExecutionContext executionContext = SecretValidatorExecutionContext.builder().setSarifReport(report) + .setValidatorConfiguration(ruleConfigurations).setTrustAllCertificates(true).build(); + when(contextFactory.create()).thenReturn(executionContext); + + when(sarifValidationSupport.findingCanBeValidated(any())).thenReturn(true); + + /* execute */ + serviceToTest.execute(); + + /* test */ + verify(contextFactory, times(1)).create(); + verify(validationService, never()).validateFindingByRegion(any(), any(), anyBoolean()); + verify(sarifEnhancementService, never()).addSerecoSeverityInfo(any(), any(), any()); + verify(sarifValidationSupport, times(1)).findingCanBeValidated(any()); + } + + @Test + void finding_location_cannot_be_validated_results_in_validation_and_categorization_never_being_called() { + SarifSchema210 report = createEmptySarifReport(); + + SecretValidatorConfigurationModel config = new SecretValidatorConfigurationModel(); + SecretValidatorCategorization categorization = mock(SecretValidatorCategorization.class); + categorization.setDefaultSeverity("high"); + config.setCategorization(categorization); + + @SuppressWarnings("unchecked") + Map ruleConfigurations = mock(Map.class); + when(ruleConfigurations.get(any())).thenReturn(config); + + SecretValidatorExecutionContext executionContext = SecretValidatorExecutionContext.builder().setSarifReport(report) + .setValidatorConfiguration(ruleConfigurations).setTrustAllCertificates(true).build(); + when(contextFactory.create()).thenReturn(executionContext); + + when(sarifValidationSupport.findingCanBeValidated(any())).thenReturn(true); + when(sarifValidationSupport.findingLocationCanBeValidated(any())).thenReturn(false); + + /* execute */ + serviceToTest.execute(); + + /* test */ + verify(contextFactory, times(1)).create(); + verify(validationService, never()).validateFindingByRegion(any(), any(), anyBoolean()); + verify(sarifEnhancementService, never()).addSerecoSeverityInfo(any(), any(), any()); + verify(sarifValidationSupport, times(1)).findingCanBeValidated(any()); + verify(sarifValidationSupport, times(1)).findingLocationCanBeValidated(any()); + } + + @Test + void valid_config_and_valid_sarif_report_results_in_validation_and_categorization_being_called_for_configured_rule() { + /* prepare */ + SecretValidatorExecutionContext executionContext = createValidExecutionContext(); + when(contextFactory.create()).thenReturn(executionContext); + + SecretValidationResult secretValidationResult = new SecretValidationResult(); + when(validationService.validateFindingByRegion(any(), any(), anyBoolean())).thenReturn(secretValidationResult); + + doNothing().when(sarifEnhancementService).addSerecoSeverityInfo(any(), any(), any()); + + when(sarifValidationSupport.findingCanBeValidated(any())).thenReturn(true); + when(sarifValidationSupport.findingLocationCanBeValidated(any())).thenReturn(true); + + /* execute */ + serviceToTest.execute(); + + /* test */ + verify(contextFactory, times(1)).create(); + verify(validationService, times(6)).validateFindingByRegion(any(), any(), anyBoolean()); + verify(sarifEnhancementService, times(6)).addSerecoSeverityInfo(any(), any(), any()); + verify(sarifValidationSupport, times(6)).findingCanBeValidated(any()); + verify(sarifValidationSupport, times(6)).findingLocationCanBeValidated(any()); + } + + private SecretValidatorExecutionContext createValidExecutionContext() { + SarifSchema210 report = createSarifReport(new File("src/test/resources/config-test-files/valid-files/test-result.txt")); + + Map ruleConfigurations = createRuleConfigurations( + new File("src/test/resources/config-test-files/valid-files/test-config.json")); + + /* @formatter:off */ + return SecretValidatorExecutionContext.builder() + .setTrustAllCertificates(true) + .setSarifReport(report) + .setValidatorConfiguration(ruleConfigurations) + .build(); + /* @formatter:on */ + } + + private SarifSchema210 createSarifReport(File file) { + try { + String sarifReportJson = TestFileReader.readTextFromFile(file); + return JSONConverter.get().fromJSON(SarifSchema210.class, sarifReportJson); + } catch (Exception e) { + throw new IllegalStateException("Creating SARIF report model from: " + file + " failed!", e); + } + } + + private Map createRuleConfigurations(File file) { + try { + String validatorConfigJson = TestFileReader.readTextFromFile(file); + SecretValidatorConfigurationModelList configurationDataList = JSONConverter.get().fromJSON(SecretValidatorConfigurationModelList.class, + validatorConfigJson); + + Map ruleConfigurations = new HashMap<>(); + for (SecretValidatorConfigurationModel configData : configurationDataList.getValidatorConfigList()) { + ruleConfigurations.put(configData.getRuleId(), configData); + } + return ruleConfigurations; + } catch (Exception e) { + throw new IllegalStateException("Creating secret validator configuration from: " + file + " failed!", e); + } + } + + private SarifSchema210 createEmptySarifReport() { + List runs = new ArrayList<>(); + + Run run = new Run(); + + List results = new ArrayList<>(); + Result result = new Result(); + + ArrayList locations = new ArrayList<>(); + locations.add(new Location()); + + result.setLocations(locations); + results.add(result); + run.setResults(results); + ; + runs.add(run); + + SarifSchema210 report = new SarifSchema210(); + report.setRuns(runs); + + return report; + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorWebRequestServiceTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorWebRequestServiceTest.java new file mode 100644 index 0000000000..3c951cbd2a --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SecretValidatorWebRequestServiceTest.java @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequest; +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorRequestHeader; +import com.mercedesbenz.sechub.wrapper.secret.validator.support.SecretValidatorHttpClientFactory; + +class SecretValidatorWebRequestServiceTest { + + private SecretValidatorWebRequestService serviceTotest; + + private SecretValidatorHttpClientFactory httpClientFactory; + private ResponseValidationService responseValidationService; + + @BeforeEach + void beforeEach() { + serviceTotest = new SecretValidatorWebRequestService(); + httpClientFactory = new SecretValidatorHttpClientFactory(); + + httpClientFactory = mock(SecretValidatorHttpClientFactory.class); + responseValidationService = mock(ResponseValidationService.class); + + serviceTotest.httpClientFactory = httpClientFactory; + serviceTotest.responseValidationService = responseValidationService; + } + + @Test + void no_finding_snippet_text_available_results_in_finding_being_skipped_from_validation() { + /* execute */ + SecretValidationResult validationResult = serviceTotest.validateFinding(null, new ArrayList<>(), true); + + /* test */ + assertEquals(SecretValidationStatus.SARIF_SNIPPET_NOT_SET, validationResult.getValidationStatus()); + } + + @Test + void no_requests_defined_results_in_finding_being_skipped_from_validation() { + /* execute */ + SecretValidationResult validationResult = serviceTotest.validateFinding("not-empty", new ArrayList<>(), true); + + /* test */ + assertEquals(SecretValidationStatus.NO_VALIDATION_CONFIGURED, validationResult.getValidationStatus()); + } + + @Test + void request_config_inside_list_is_null_results_request_will_be_skipped() { + /* prepare */ + ArrayList requests = new ArrayList<>(); + requests.add(null); + + /* execute */ + SecretValidationResult validationResult = serviceTotest.validateFinding("not-empty", requests, true); + + /* test */ + // no validation request was performed ends up with the following status, only + // if at least 1 request was performed, the finding could be marked as invalid. + assertEquals(SecretValidationStatus.ALL_VALIDATION_REQUESTS_FAILED, validationResult.getValidationStatus()); + } + + @Test + void request_url_is_null_results_request_will_be_skipped() { + /* prepare */ + ArrayList requests = new ArrayList<>(); + requests.add(new SecretValidatorRequest()); + + /* execute */ + SecretValidationResult validationResult = serviceTotest.validateFinding("no-empty", requests, true); + + /* test */ + // no validation request was performed ends up with the following status, only + // if at least 1 request was performed, the finding could be marked as invalid. + assertEquals(SecretValidationStatus.ALL_VALIDATION_REQUESTS_FAILED, validationResult.getValidationStatus()); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void proxy_required_calls_the_correct_http_client_results_validation_result(boolean expectedValidation) throws IOException, InterruptedException { + /* prepare */ + List requests = createListOfRequests(true); + + HttpClient proxyHttpClient = mock(HttpClient.class); + when(proxyHttpClient.send(any(), any())).thenReturn(null); + + when(httpClientFactory.createProxyHttpClient(anyBoolean())).thenReturn(proxyHttpClient); + + when(responseValidationService.isValidResponse(any(), any())).thenReturn(expectedValidation); + + /* execute */ + SecretValidationResult validationResult = serviceTotest.validateFinding("no-empty", requests, true); + + /* test */ + if (expectedValidation) { + assertEquals(SecretValidationStatus.VALID, validationResult.getValidationStatus()); + assertEquals("http://example.com", validationResult.getValidatedByUrl()); + } else { + assertEquals(SecretValidationStatus.ALL_VALIDATION_REQUESTS_FAILED, validationResult.getValidationStatus()); + } + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void no_proxy_required_calls_the_correct_http_client_results_validation_result(boolean expectedValidation) throws IOException, InterruptedException { + /* prepare */ + List requests = createListOfRequests(false); + + HttpClient directHttpClient = mock(HttpClient.class); + @SuppressWarnings("unchecked") + HttpResponse response = mock(HttpResponse.class); + when(directHttpClient.send(any(), any())).thenReturn(response); + + when(httpClientFactory.createDirectHttpClient(anyBoolean())).thenReturn(directHttpClient); + + when(responseValidationService.isValidResponse(any(), any())).thenReturn(expectedValidation); + + /* execute */ + SecretValidationResult validationResult = serviceTotest.validateFinding("no-empty", requests, true); + + /* test */ + if (expectedValidation) { + assertEquals(SecretValidationStatus.VALID, validationResult.getValidationStatus()); + assertEquals("http://example.com", validationResult.getValidatedByUrl()); + } else { + assertEquals(SecretValidationStatus.INVALID, validationResult.getValidationStatus()); + } + } + + private List createListOfRequests(boolean proxyRequired) throws MalformedURLException { + ArrayList requests = new ArrayList<>(); + SecretValidatorRequest secretValidatorRequest = new SecretValidatorRequest(); + secretValidatorRequest.setProxyRequired(proxyRequired); + secretValidatorRequest.setUrl(new URL("http://example.com")); + List headers = new ArrayList<>(); + SecretValidatorRequestHeader header = new SecretValidatorRequestHeader(); + header.setName("Authorization"); + headers.add(header); + secretValidatorRequest.setHeaders(headers); + requests.add(secretValidatorRequest); + return requests; + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SerecoSeveritySarifEnhancementServiceTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SerecoSeveritySarifEnhancementServiceTest.java new file mode 100644 index 0000000000..3dcca39b51 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/execution/SerecoSeveritySarifEnhancementServiceTest.java @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.execution; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.net.MalformedURLException; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.wrapper.secret.validator.model.SecretValidatorCategorization; +import com.mercedesbenz.sechub.wrapper.secret.validator.support.SarifImporterKeys; + +import de.jcup.sarif_2_1_0.model.PropertyBag; +import de.jcup.sarif_2_1_0.model.Region; + +class SerecoSeveritySarifEnhancementServiceTest { + + private SerecoSeveritySarifEnhancementService serviceToTest = new SerecoSeveritySarifEnhancementService(); + + @Test + void categorization_config_is_null_results_in_property_bag_being_empty() { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, null); + + /* test */ + assertNull(findingRegion.getProperties()); + } + + @Test + void categorization_config_is_empty_results_in_property_bag_being_empty() { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, categorization); + + /* test */ + assertNull(findingRegion.getProperties()); + } + + @Test + void validation_result_valid_results_in_configured_categorization() { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(SecretValidationStatus.VALID); + validationResult.setValidatedByUrl("http://api.example.com"); + + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + categorization.setValidationSuccessSeverity("critical"); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, categorization); + + /* test */ + PropertyBag properties = findingRegion.getProperties(); + Map additionalProperties = properties.getAdditionalProperties(); + + assertEquals(2, additionalProperties.size()); + assertEquals(categorization.getValidationSuccessSeverity(), additionalProperties.get(SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey())); + assertEquals("http://api.example.com", additionalProperties.get(SarifImporterKeys.SECRETSCAN_VALIDATED_BY_URL.getKey())); + } + + @Test + void validation_result_invalid_results_in_configured_categorization() { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(SecretValidationStatus.INVALID); + + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + categorization.setValidationFailedSeverity("low"); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, categorization); + + /* test */ + PropertyBag properties = findingRegion.getProperties(); + Map additionalProperties = properties.getAdditionalProperties(); + + assertEquals(1, additionalProperties.size()); + assertEquals(categorization.getValidationFailedSeverity(), additionalProperties.get(SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey())); + } + + @Test + void validation_result_with_no_validation_configured_results_in_default_categorization() { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(SecretValidationStatus.NO_VALIDATION_CONFIGURED); + + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + categorization.setDefaultSeverity("medium"); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, categorization); + + /* test */ + PropertyBag properties = findingRegion.getProperties(); + Map additionalProperties = properties.getAdditionalProperties(); + + assertEquals(1, additionalProperties.size()); + assertEquals(categorization.getDefaultSeverity(), additionalProperties.get(SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey())); + } + + @Test + void validation_result_with_sarif_snippet_not_set_results_in_default_categorization() { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(SecretValidationStatus.SARIF_SNIPPET_NOT_SET); + + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + categorization.setDefaultSeverity("medium"); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, categorization); + + /* test */ + PropertyBag properties = findingRegion.getProperties(); + Map additionalProperties = properties.getAdditionalProperties(); + + assertEquals(1, additionalProperties.size()); + assertEquals(categorization.getDefaultSeverity(), additionalProperties.get(SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey())); + } + + @Test + void validation_empty_results_in_default_categorization() { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + categorization.setDefaultSeverity("medium"); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, categorization); + + /* test */ + PropertyBag properties = findingRegion.getProperties(); + Map additionalProperties = properties.getAdditionalProperties(); + + assertEquals(1, additionalProperties.size()); + assertEquals(categorization.getDefaultSeverity(), additionalProperties.get(SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey())); + } + + @Test + void validation_result_valid_results_in_severity_value_is_null() throws MalformedURLException { + /* prepare */ + Region findingRegion = new Region(); + SecretValidationResult validationResult = new SecretValidationResult(); + validationResult.setValidationStatus(SecretValidationStatus.VALID); + + // we do not configure a successful validation category here + // this should not happen because we create the validation config file step by + // step, + // but we need to make sure this will not fail in any case + SecretValidatorCategorization categorization = new SecretValidatorCategorization(); + categorization.setValidationFailedSeverity("low"); + + /* execute */ + serviceToTest.addSerecoSeverityInfo(validationResult, findingRegion, categorization); + + /* test */ + PropertyBag properties = findingRegion.getProperties(); + Map additionalProperties = properties.getAdditionalProperties(); + + assertEquals(1, additionalProperties.size()); + assertNull(additionalProperties.get(SarifImporterKeys.SECRETSCAN_SERECO_SEVERITY.getKey())); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModelListTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModelListTest.java new file mode 100644 index 0000000000..c038acc6a6 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/model/SecretValidatorConfigurationModelListTest.java @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.model.JSONConverter; + +class SecretValidatorConfigurationModelListTest { + + @Test + void json_converter_can_handle_model_in_expected_way() { + /* prepare */ + String json = """ + { + "validatorConfigList" : [ { + "ruleId" : "rule-id", + "categorization" : { + "defaultSeverity" : "high", + "validationFailedSeverity" : "medium", + "validationSuccessSeverity" : "critical" + }, + "requests" : [ { + "url" : "https://api.example.com", + "proxyRequired" : true, + "headers" : [ { + "name" : "Authorization", + "valuePrefix" : "Bearer" + } ], + "expectedResponse" : { + "httpStatus" : 200, + "contains" : { + "allOf" : [ "is", "there" ], + "oneOf" : [ "success" ] + } + } + } ] + } ] + } + """; + + String expected = "{\"validatorConfigList\":[{\"ruleId\":\"rule-id\",\"categorization\":{\"defaultSeverity\":\"high\",\"validationFailedSeverity\":\"medium\",\"validationSuccessSeverity\":\"critical\"}," + + "\"requests\":[{\"url\":\"https://api.example.com\",\"proxyRequired\":true,\"headers\":[{\"name\":\"Authorization\",\"valuePrefix\":\"Bearer\"}]," + + "\"expectedResponse\":{\"httpStatus\":200,\"contains\":{\"allOf\":[\"is\",\"there\"],\"oneOf\":[\"success\"]}}}]}]}"; + + /* execute */ + SecretValidatorConfigurationModelList fromJson = JSONConverter.get().fromJSON(SecretValidatorConfigurationModelList.class, json); + String toJson = JSONConverter.get().toJSON(fromJson); + + /* test */ + assertEquals(expected, toJson); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorConfigurationSpringBootTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorConfigurationSpringBootTest.java new file mode 100644 index 0000000000..08f88e2f02 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorConfigurationSpringBootTest.java @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.properties; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@SpringBootTest +@ExtendWith(SpringExtension.class) +@EnableConfigurationProperties +@TestPropertySource(properties = { "secret.validator.config-file=src/test/resources/config-test-files/valid-files/test-config.json", + "secret.validator.trust-all-certificates=false", "pds.job.result.file=src/test/resources/config-test-files/valid-files/test-result.txt" }) +@ActiveProfiles("test") +class SecretValidatorConfigurationSpringBootTest { + + @Autowired + private SecretValidatorProperties properties; + + @Autowired + private SecretValidatorPDSJobResult pdsJobResult; + + @Test + void properties_are_created_correctly() { + /* test */ + + // check if all SecretValidatorProperties are as expected + assertFalse(properties.isTrustAllCertificates()); + assertEquals("src/test/resources/config-test-files/valid-files/test-config.json", properties.getConfigFile().toString()); + + // check if the PDS job result file is as expected + assertEquals("src/test/resources/config-test-files/valid-files/test-result.txt", pdsJobResult.getFile().toString()); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPDSJobResultTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPDSJobResultTest.java new file mode 100644 index 0000000000..3774fb5534 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPDSJobResultTest.java @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +class SecretValidatorPDSJobResultTest { + + @Test + void pds_job_result_file_is_null_throws_exception() { + /* execute + test */ + assertThrows(IllegalArgumentException.class, () -> new SecretValidatorPDSJobResult(null)); + } + + @Test + void not_existing_pds_job_result_file_throws_exception() { + /* prepare */ + File notExisting = mock(File.class); + when(notExisting.exists()).thenReturn(false); + + /* execute + test */ + assertThrows(IllegalArgumentException.class, () -> new SecretValidatorPDSJobResult(notExisting)); + } + + @Test + void not_readable_pds_job_result_file_throws_exception() { + /* prepare */ + File notReadable = mock(File.class); + when(notReadable.exists()).thenReturn(true); + when(notReadable.canRead()).thenReturn(false); + + /* execute + test */ + assertThrows(IllegalArgumentException.class, () -> new SecretValidatorPDSJobResult(notReadable)); + } + + @Test + void valid_properties_result_in_valid_configuration() { + /* prepare */ + File validConfigFile = new File("src/test/resources/config-test-files/valid-files/test-result.txt"); + + /* execute */ + SecretValidatorPDSJobResult pdsJobResult = new SecretValidatorPDSJobResult(validConfigFile); + + /* test */ + assertEquals(validConfigFile, pdsJobResult.getFile()); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPropertiesTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPropertiesTest.java new file mode 100644 index 0000000000..1700d787b9 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/properties/SecretValidatorPropertiesTest.java @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.properties; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; + +import org.junit.jupiter.api.Test; + +class SecretValidatorPropertiesTest { + + @Test + void validator_config_file_is_null_throws_exception() { + /* execute + test */ + assertThrows(IllegalArgumentException.class, () -> new SecretValidatorProperties(null, false)); + } + + @Test + void not_existing_validator_config_file_throws_exception() { + /* prepare */ + File notExisting = mock(File.class); + when(notExisting.exists()).thenReturn(false); + + /* execute + test */ + assertThrows(IllegalArgumentException.class, () -> new SecretValidatorProperties(notExisting, false)); + } + + @Test + void not_readable_validator_config_file_throws_exception() { + /* prepare */ + File notReadable = mock(File.class); + when(notReadable.exists()).thenReturn(true); + when(notReadable.canRead()).thenReturn(false); + + /* execute + test */ + assertThrows(IllegalArgumentException.class, () -> new SecretValidatorProperties(notReadable, false)); + } + + @Test + void valid_properties_result_in_valid_configuration() { + /* prepare */ + File validConfigFile = new File("src/test/resources/config-test-files/valid-files/test-config.json"); + + /* execute */ + SecretValidatorProperties properties = new SecretValidatorProperties(validConfigFile, true); + + /* test */ + assertEquals(validConfigFile, properties.getConfigFile()); + assertTrue(properties.isTrustAllCertificates()); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifValidationSupportTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifValidationSupportTest.java new file mode 100644 index 0000000000..9c2f8a3251 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SarifValidationSupportTest.java @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.support; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import de.jcup.sarif_2_1_0.model.Location; +import de.jcup.sarif_2_1_0.model.PhysicalLocation; +import de.jcup.sarif_2_1_0.model.Region; +import de.jcup.sarif_2_1_0.model.Result; + +class SarifValidationSupportTest { + + private SarifValidationSupport supportToTest = new SarifValidationSupport(); + + @Test + void finding_null_returns_false() { + /* execute */ + boolean findingCanBeValidated = supportToTest.findingCanBeValidated(null); + + /* test */ + assertFalse(findingCanBeValidated); + } + + @Test + void finding_locations_is_null_returns_false() { + /* prepare */ + Result finding = new Result(); + finding.setLocations(null); + + /* execute */ + boolean findingCanBeValidated = supportToTest.findingCanBeValidated(finding); + + /* test */ + assertFalse(findingCanBeValidated); + } + + @Test + void finding_locations_is_empty_returns_false() { + /* prepare */ + Result finding = new Result(); + finding.setLocations(new ArrayList<>()); + + /* execute */ + boolean findingCanBeValidated = supportToTest.findingCanBeValidated(finding); + + /* test */ + assertFalse(findingCanBeValidated); + } + + @Test + void finding_locations_conatins_one_location_object_returns_true() { + /* prepare */ + List locations = new ArrayList<>(); + locations.add(new Location()); + Result finding = new Result(); + finding.setLocations(locations); + + /* execute */ + boolean findingCanBeValidated = supportToTest.findingCanBeValidated(finding); + + /* test */ + assertTrue(findingCanBeValidated); + } + + @Test + void finding_location_is_null_returns_false() { + /* execute */ + boolean findingCanBeValidated = supportToTest.findingLocationCanBeValidated(null); + + /* test */ + assertFalse(findingCanBeValidated); + } + + @Test + void finding_location_physical_location_is_null_returns_false() { + /* prepare */ + Location location = new Location(); + location.setPhysicalLocation(null); + + /* execute */ + boolean findingCanBeValidated = supportToTest.findingLocationCanBeValidated(location); + + /* test */ + assertFalse(findingCanBeValidated); + } + + @Test + void finding_location_physical_location_region_is_null_returns_false() { + /* prepare */ + PhysicalLocation physicalLocation = new PhysicalLocation(); + physicalLocation.setRegion(null); + Location location = new Location(); + location.setPhysicalLocation(physicalLocation); + + /* execute */ + boolean findingCanBeValidated = supportToTest.findingLocationCanBeValidated(location); + + /* test */ + assertFalse(findingCanBeValidated); + } + + @Test + void finding_location_physical_location_region_is_not_null_returns_false() { + /* prepare */ + PhysicalLocation physicalLocation = new PhysicalLocation(); + physicalLocation.setRegion(new Region()); + Location location = new Location(); + location.setPhysicalLocation(physicalLocation); + + /* execute */ + boolean findingCanBeValidated = supportToTest.findingLocationCanBeValidated(location); + + /* test */ + assertTrue(findingCanBeValidated); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SecretValidatorHttpClientFactoryTest.java b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SecretValidatorHttpClientFactoryTest.java new file mode 100644 index 0000000000..b577f34141 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/java/com/mercedesbenz/sechub/wrapper/secret/validator/support/SecretValidatorHttpClientFactoryTest.java @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.wrapper.secret.validator.support; + +import static org.junit.Assert.assertTrue; + +import java.net.http.HttpClient; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class SecretValidatorHttpClientFactoryTest { + + private SecretValidatorHttpClientFactory factoryToTest = new SecretValidatorHttpClientFactory(); + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void create_proxy_http_client_returns_expected_http_client(boolean trustAllCertificates) { + /* execute */ + HttpClient proxyHttpClient = factoryToTest.createProxyHttpClient(trustAllCertificates); + + /* test */ + assertTrue(proxyHttpClient.proxy().isPresent()); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void create_direct_http_client_returns_expected_http_client(boolean trustAllCertificates) { + /* execute */ + HttpClient proxyHttpClient = factoryToTest.createDirectHttpClient(trustAllCertificates); + + /* test */ + assertTrue(proxyHttpClient.proxy().isEmpty()); + } + +} diff --git a/sechub-wrapper-secret-validator/src/test/resources/config-test-files/invalid-files/invalid-sarif.txt b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/invalid-files/invalid-sarif.txt new file mode 100644 index 0000000000..fb11c48106 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/invalid-files/invalid-sarif.txt @@ -0,0 +1 @@ +not valid sarif \ No newline at end of file diff --git a/sechub-wrapper-secret-validator/src/test/resources/config-test-files/invalid-files/invalid-validator-config.txt b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/invalid-files/invalid-validator-config.txt new file mode 100644 index 0000000000..a6621a3896 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/invalid-files/invalid-validator-config.txt @@ -0,0 +1 @@ +invalid file \ No newline at end of file diff --git a/sechub-wrapper-secret-validator/src/test/resources/config-test-files/valid-files/test-config.json b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/valid-files/test-config.json new file mode 100644 index 0000000000..9bdf28c27e --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/valid-files/test-config.json @@ -0,0 +1,25 @@ +{ + "validatorConfigList" : [ { + "ruleId" : "generic-api-key", + "categorization" : { + "defaultSeverity" : "high", + "validationFailedSeverity" : "medium", + "validationSuccessSeverity" : "critical" + }, + "requests" : [ { + "proxyRequired" : false, + "url" : "https://localhost", + "headers" : [ { + "name" : "Authorization", + "valuePrefix" : "Bearer" + } ], + "expectedResponse" : { + "httpStatus" : 200, + "contains" : { + "allOf" : [ "is", "there" ], + "oneOf" : [ "success" ] + } + } + } ] + } ] +} \ No newline at end of file diff --git a/sechub-wrapper-secret-validator/src/test/resources/config-test-files/valid-files/test-result.txt b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/valid-files/test-result.txt new file mode 100644 index 0000000000..8dc507b517 --- /dev/null +++ b/sechub-wrapper-secret-validator/src/test/resources/config-test-files/valid-files/test-result.txt @@ -0,0 +1,974 @@ +{ + "$schema" : "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", + "version" : "2.1.0", + "runs" : [ { + "tool" : { + "driver" : { + "name" : "gitleaks", + "semanticVersion" : "v8.0.0", + "rules" : [ { + "id" : "adafruit-api-key", + "name" : "Adafruit API Key", + "shortDescription" : { + "text" : "(?i)(?:adafruit)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "adobe-client-id", + "name" : "Adobe Client ID (Oauth Web)", + "shortDescription" : { + "text" : "(?i)(?:adobe)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "adobe-client-secret", + "name" : "Adobe Client Secret", + "shortDescription" : { + "text" : "(?i)\\b((p8e-)(?i)[a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "age secret key", + "name" : "Age secret key", + "shortDescription" : { + "text" : "AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}" + } + }, { + "id" : "airtable-api-key", + "name" : "Airtable API Key", + "shortDescription" : { + "text" : "(?i)(?:airtable)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{17})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "algolia-api-key", + "name" : "Algolia API Key", + "shortDescription" : { + "text" : "(?i)(?:algolia)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "alibaba-access-key-id", + "name" : "Alibaba AccessKey ID", + "shortDescription" : { + "text" : "(?i)\\b((LTAI)(?i)[a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "alibaba-secret-key", + "name" : "Alibaba Secret Key", + "shortDescription" : { + "text" : "(?i)(?:alibaba)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "asana-client-id", + "name" : "Asana Client ID", + "shortDescription" : { + "text" : "(?i)(?:asana)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "asana-client-secret", + "name" : "Asana Client Secret", + "shortDescription" : { + "text" : "(?i)(?:asana)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "atlassian-api-token", + "name" : "Atlassian API token", + "shortDescription" : { + "text" : "(?i)(?:atlassian|confluence|jira)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "aws-access-token", + "name" : "AWS", + "shortDescription" : { + "text" : "(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}" + } + }, { + "id" : "bitbucket-client-id", + "name" : "BitBucket Client ID", + "shortDescription" : { + "text" : "(?i)(?:bitbucket)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bitbucket-client-secret", + "name" : "BitBucket Client Secret", + "shortDescription" : { + "text" : "(?i)(?:bitbucket)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bittrex-access-key", + "name" : "Bittrex Access Key", + "shortDescription" : { + "text" : "(?i)(?:bittrex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "bittrex-secret-key", + "name" : "Bittrex Secret Key", + "shortDescription" : { + "text" : "(?i)(?:bittrex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "beamer-api-token", + "name" : "Beamer API token", + "shortDescription" : { + "text" : "(?i)(?:beamer)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(b_[a-z0-9=_\\-]{44})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "codecov-access-token", + "name" : "Codecov Access Token", + "shortDescription" : { + "text" : "(?i)(?:codecov)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "coinbase-access-token", + "name" : "Coinbase Access Token", + "shortDescription" : { + "text" : "(?i)(?:coinbase)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "clojars-api-token", + "name" : "Clojars API token", + "shortDescription" : { + "text" : "(?i)(CLOJARS_)[a-z0-9]{60}" + } + }, { + "id" : "confluent-access-token", + "name" : "Confluent Access Token", + "shortDescription" : { + "text" : "(?i)(?:confluent)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "confluent-secret-key", + "name" : "Confluent Secret Key", + "shortDescription" : { + "text" : "(?i)(?:confluent)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "contentful-delivery-api-token", + "name" : "Contentful delivery API token", + "shortDescription" : { + "text" : "(?i)(?:contentful)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{43})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "databricks-api-token", + "name" : "Databricks API token", + "shortDescription" : { + "text" : "(?i)\\b(dapi[a-h0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "datadog-access-token", + "name" : "Datadog Access Token", + "shortDescription" : { + "text" : "(?i)(?:datadog)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-api-token", + "name" : "Discord API key", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-client-id", + "name" : "Discord client ID", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{18})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "discord-client-secret", + "name" : "Discord client secret", + "shortDescription" : { + "text" : "(?i)(?:discord)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "doppler-api-token", + "name" : "Doppler API token", + "shortDescription" : { + "text" : "(dp\\.pt\\.)(?i)[a-z0-9]{43}" + } + }, { + "id" : "dropbox-api-token", + "name" : "Dropbox API secret", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{15})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "dropbox-long-lived-api-token", + "name" : "Dropbox long lived API token", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\\-_=]{43})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "dropbox-short-lived-api-token", + "name" : "Dropbox short lived API token", + "shortDescription" : { + "text" : "(?i)(?:dropbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(sl\\.[a-z0-9\\-=_]{135})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "droneci-access-token", + "name" : "Droneci Access Token", + "shortDescription" : { + "text" : "(?i)(?:droneci)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "duffel-api-token", + "name" : "Duffel API token", + "shortDescription" : { + "text" : "duffel_(test|live)_(?i)[a-z0-9_\\-=]{43}" + } + }, { + "id" : "dynatrace-api-token", + "name" : "Dynatrace API token", + "shortDescription" : { + "text" : "dt0c01\\.(?i)[a-z0-9]{24}\\.[a-z0-9]{64}" + } + }, { + "id" : "easypost-api-token", + "name" : "EasyPost API token", + "shortDescription" : { + "text" : "EZAK(?i)[a-z0-9]{54}" + } + }, { + "id" : "easypost-test-api-token", + "name" : "EasyPost test API token", + "shortDescription" : { + "text" : "EZTK(?i)[a-z0-9]{54}" + } + }, { + "id" : "etsy-access-token", + "name" : "Etsy Access Token", + "shortDescription" : { + "text" : "(?i)(?:etsy)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "facebook", + "name" : "facebook", + "shortDescription" : { + "text" : "(?i)(?:facebook)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "fastly-api-token", + "name" : "Fastly API key", + "shortDescription" : { + "text" : "(?i)(?:fastly)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finicity-client-secret", + "name" : "Finicity Client Secret", + "shortDescription" : { + "text" : "(?i)(?:finicity)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finicity-api-token", + "name" : "Finicity API token", + "shortDescription" : { + "text" : "(?i)(?:finicity)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "flickr-access-token", + "name" : "Flickr Access Token", + "shortDescription" : { + "text" : "(?i)(?:flickr)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "finnhub-access-token", + "name" : "Finnhub Access Token", + "shortDescription" : { + "text" : "(?i)(?:finnhub)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "flutterwave-public-key", + "name" : "Finicity Public Key", + "shortDescription" : { + "text" : "FLWPUBK_TEST-(?i)[a-h0-9]{32}-X" + } + }, { + "id" : "flutterwave-secret-key", + "name" : "Flutterwave Secret Key", + "shortDescription" : { + "text" : "FLWSECK_TEST-(?i)[a-h0-9]{32}-X" + } + }, { + "id" : "flutterwave-encryption-key", + "name" : "Flutterwave Encryption Key", + "shortDescription" : { + "text" : "FLWSECK_TEST-(?i)[a-h0-9]{12}" + } + }, { + "id" : "frameio-api-token", + "name" : "Frame.io API token", + "shortDescription" : { + "text" : "fio-u-(?i)[a-z0-9\\-_=]{64}" + } + }, { + "id" : "freshbooks-access-token", + "name" : "Freshbooks Access Token", + "shortDescription" : { + "text" : "(?i)(?:freshbooks)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "gocardless-api-token", + "name" : "GoCardless API token", + "shortDescription" : { + "text" : "(?i)(?:gocardless)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(live_(?i)[a-z0-9\\-_=]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "gcp-api-key", + "name" : "GCP API key", + "shortDescription" : { + "text" : "(?i)\\b(AIza[0-9A-Za-z\\\\-_]{35})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "github-pat", + "name" : "GitHub Personal Access Token", + "shortDescription" : { + "text" : "ghp_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-oauth", + "name" : "GitHub OAuth Access Token", + "shortDescription" : { + "text" : "gho_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-app-token", + "name" : "GitHub App Token", + "shortDescription" : { + "text" : "(ghu|ghs)_[0-9a-zA-Z]{36}" + } + }, { + "id" : "github-refresh-token", + "name" : "GitHub Refresh Token", + "shortDescription" : { + "text" : "ghr_[0-9a-zA-Z]{36}" + } + }, { + "id" : "gitlab-pat", + "name" : "Gitlab Personal Access Token", + "shortDescription" : { + "text" : "glpat-[0-9a-zA-Z\\-\\_]{20}" + } + }, { + "id" : "gitter-access-token", + "name" : "Gitter Access Token", + "shortDescription" : { + "text" : "(?i)(?:gitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "hashicorp-tf-api-token", + "name" : "HashiCorp Terraform user/org API token", + "shortDescription" : { + "text" : "(?i)[a-z0-9]{14}\\.atlasv1\\.[a-z0-9\\-_=]{60,70}" + } + }, { + "id" : "heroku-api-key", + "name" : "Heroku API Key", + "shortDescription" : { + "text" : "(?i)(?:heroku)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "hubspot-api-key", + "name" : "HubSpot API Token", + "shortDescription" : { + "text" : "(?i)(?:hubspot)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "intercom-api-key", + "name" : "Intercom API Token", + "shortDescription" : { + "text" : "(?i)(?:intercom)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{60})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kraken-access-token", + "name" : "Kraken Access Token", + "shortDescription" : { + "text" : "(?i)(?:kraken)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9\\/=_\\+\\-]{80,90})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kucoin-access-token", + "name" : "Kucoin Access Token", + "shortDescription" : { + "text" : "(?i)(?:kucoin)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "kucoin-secret-key", + "name" : "Kucoin Secret Key", + "shortDescription" : { + "text" : "(?i)(?:kucoin)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "launchdarkly-access-token", + "name" : "Launchdarkly Access Token", + "shortDescription" : { + "text" : "(?i)(?:launchdarkly)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linear-api-key", + "name" : "Linear API Token", + "shortDescription" : { + "text" : "lin_api_(?i)[a-z0-9]{40}" + } + }, { + "id" : "linear-client-secret", + "name" : "Linear Client Secret", + "shortDescription" : { + "text" : "(?i)(?:linear)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linkedin-client-id", + "name" : "LinkedIn Client ID", + "shortDescription" : { + "text" : "(?i)(?:linkedin|linked-in)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{14})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "linkedin-client-secret", + "name" : "LinkedIn Client secret", + "shortDescription" : { + "text" : "(?i)(?:linkedin|linked-in)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "lob-api-key", + "name" : "Lob API Key", + "shortDescription" : { + "text" : "(?i)(?:lob)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}((live|test)_[a-f0-9]{35})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "lob-pub-api-key", + "name" : "Lob Publishable API Key", + "shortDescription" : { + "text" : "(?i)(?:lob)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}((test|live)_pub_[a-f0-9]{31})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailchimp-api-key", + "name" : "Mailchimp API key", + "shortDescription" : { + "text" : "(?i)(?:mailchimp)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{32}-us20)(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-pub-key", + "name" : "Mailgun public validation key", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(pubkey-[a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-private-api-token", + "name" : "Mailgun private API token", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(key-[a-f0-9]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mailgun-signing-key", + "name" : "Mailgun webhook signing key", + "shortDescription" : { + "text" : "(?i)(?:mailgun)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mapbox-api-token", + "name" : "MapBox API token", + "shortDescription" : { + "text" : "(?i)(?:mapbox)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(pk\\.[a-z0-9]{60}\\.[a-z0-9]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "mattermost-access-token", + "name" : "Mattermost Access Token", + "shortDescription" : { + "text" : "(?i)(?:mattermost)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{26})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "messagebird-api-token", + "name" : "MessageBird API token", + "shortDescription" : { + "text" : "(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{25})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "messagebird-client-id", + "name" : "MessageBird client ID", + "shortDescription" : { + "text" : "(?i)(?:messagebird|message-bird|message_bird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "netlify-access-token", + "name" : "Netlify Access Token", + "shortDescription" : { + "text" : "(?i)(?:netlify)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{40,46})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-user-api-key", + "name" : "New Relic user API Key", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(NRAK-[a-z0-9]{27})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-user-api-id", + "name" : "New Relic user API ID", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "new-relic-browser-api-token", + "name" : "New Relic ingest browser API token", + "shortDescription" : { + "text" : "(?i)(?:new-relic|newrelic|new_relic)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(NRJS-[a-f0-9]{19})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "npm-access-token", + "name" : "npm access token", + "shortDescription" : { + "text" : "(?i)\\b(npm_[a-z0-9]{36})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "nytimes-access-token", + "name" : "Nytimes Access Token", + "shortDescription" : { + "text" : "(?i)(?:nytimes|new-york-times,|newyorktimes)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{32})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "okta-access-token", + "name" : "Okta Access Token", + "shortDescription" : { + "text" : "(?i)(?:okta)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9=_\\-]{42})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-client-id", + "name" : "Plaid Client ID", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{24})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-secret-key", + "name" : "Plaid Secret key", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "plaid-api-token", + "name" : "Plaid API Token", + "shortDescription" : { + "text" : "(?i)(?:plaid)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(access-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-password", + "name" : "PlanetScale password", + "shortDescription" : { + "text" : "(?i)\\b(pscale_pw_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-api-token", + "name" : "PlanetScale API token", + "shortDescription" : { + "text" : "(?i)\\b(pscale_tkn_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "planetscale-oauth-token", + "name" : "PlanetScale OAuth token", + "shortDescription" : { + "text" : "(?i)\\b(pscale_oauth_(?i)[a-z0-9=\\-_\\.]{32,64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "postman-api-token", + "name" : "Postman API token", + "shortDescription" : { + "text" : "(?i)\\b(PMAK-(?i)[a-f0-9]{24}\\-[a-f0-9]{34})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "private-key", + "name" : "Private Key", + "shortDescription" : { + "text" : "(?i)-----BEGIN[ A-Z0-9_-]{0,100}PRIVATE KEY-----[\\s\\S-]*KEY----" + } + }, { + "id" : "pulumi-api-token", + "name" : "Pulumi API token", + "shortDescription" : { + "text" : "(?i)\\b(pul-[a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "pypi-upload-token", + "name" : "PyPI upload token", + "shortDescription" : { + "text" : "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\\-_]{50,1000}" + } + }, { + "id" : "rubygems-api-token", + "name" : "Rubygem API token", + "shortDescription" : { + "text" : "(?i)\\b(rubygems_[a-f0-9]{48})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "rapidapi-access-token", + "name" : "RapidAPI Access Token", + "shortDescription" : { + "text" : "(?i)(?:rapidapi)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9_-]{50})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendbird-access-id", + "name" : "Sendbird Access ID", + "shortDescription" : { + "text" : "(?i)(?:sendbird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendbird-access-token", + "name" : "Sendbird Access Token", + "shortDescription" : { + "text" : "(?i)(?:sendbird)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendgrid-api-token", + "name" : "SendGrid API token", + "shortDescription" : { + "text" : "(?i)\\b(SG\\.(?i)[a-z0-9=_\\-\\.]{66})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sendinblue-api-token", + "name" : "Sendinblue API token", + "shortDescription" : { + "text" : "(?i)\\b(xkeysib-[a-f0-9]{64}\\-(?i)[a-z0-9]{16})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sentry-access-token", + "name" : "Sentry Access Token", + "shortDescription" : { + "text" : "(?i)(?:sentry)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-f0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "shippo-api-token", + "name" : "Shippo API token", + "shortDescription" : { + "text" : "(?i)\\b(shippo_(live|test)_[a-f0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "shopify-access-token", + "name" : "Shopify access token", + "shortDescription" : { + "text" : "shpat_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-custom-access-token", + "name" : "Shopify custom access token", + "shortDescription" : { + "text" : "shpca_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-private-app-access-token", + "name" : "Shopify private app access token", + "shortDescription" : { + "text" : "shppa_[a-fA-F0-9]{32}" + } + }, { + "id" : "shopify-shared-secret", + "name" : "Shopify shared secret", + "shortDescription" : { + "text" : "shpss_[a-fA-F0-9]{32}" + } + }, { + "id" : "slack-access-token", + "name" : "Slack token", + "shortDescription" : { + "text" : "xox[baprs]-([0-9a-zA-Z]{10,48})" + } + }, { + "id" : "slack-web-hook", + "name" : "Slack Webhook", + "shortDescription" : { + "text" : "https:\\/\\/hooks.slack.com\\/services\\/[A-Za-z0-9+\\/]{44,46}" + } + }, { + "id" : "stripe-access-token", + "name" : "Stripe", + "shortDescription" : { + "text" : "(?i)(sk|pk)_(test|live)_[0-9a-z]{10,32}" + } + }, { + "id" : "square-access-token", + "name" : "Square Access Token", + "shortDescription" : { + "text" : "(?i)\\b(sq0atp-[0-9A-Za-z\\-_]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "squarespace-access-token", + "name" : "Squarespace Access Token", + "shortDescription" : { + "text" : "(?i)(?:squarespace)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sumologic-access-id", + "name" : "SumoLogic Access ID", + "shortDescription" : { + "text" : "(?i)(?:sumo)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{14})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "sumologic-access-token", + "name" : "SumoLogic Access Token", + "shortDescription" : { + "text" : "(?i)(?:sumo)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{64})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "travisci-access-token", + "name" : "Travis CI Access Token", + "shortDescription" : { + "text" : "(?i)(?:travis)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{22})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twilio-api-key", + "name" : "Twilio API Key", + "shortDescription" : { + "text" : "SK[0-9a-fA-F]{32}" + } + }, { + "id" : "twitch-api-token", + "name" : "Twitch API token", + "shortDescription" : { + "text" : "(?i)(?:twitch)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{30})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-api-key", + "name" : "Twitter API Key", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{25})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-api-secret", + "name" : "Twitter API Secret", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{50})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-access-token", + "name" : "Twitter Access Token", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9]{15,25}-[a-zA-Z0-9]{20,40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-access-secret", + "name" : "Twitter Access Secret", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{45})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "twitter-bearer-token", + "name" : "Twitter Bearer Token", + "shortDescription" : { + "text" : "(?i)(?:twitter)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(A{22}[a-zA-Z0-9%]{80,100})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "typeform-api-token", + "name" : "Typeform API token", + "shortDescription" : { + "text" : "(?i)(?:typeform)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(tfp_[a-z0-9\\-_\\.=]{59})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-api-key", + "name" : "Yandex API Key", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(AQVN[A-Za-z0-9_\\-]{35,38})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-aws-access-token", + "name" : "Yandex AWS Access Token", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(YC[a-zA-Z0-9_\\-]{38})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "yandex-access-token", + "name" : "Yandex Access Token", + "shortDescription" : { + "text" : "(?i)(?:yandex)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}(t1\\.[A-Z0-9a-z_-]+[=]{0,2}\\.[A-Z0-9a-z_-]{86}[=]{0,2})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "zendesk-secret-key", + "name" : "Zendesk Secret Key", + "shortDescription" : { + "text" : "(?i)(?:zendesk)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([a-z0-9]{40})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + }, { + "id" : "generic-api-key", + "name" : "Generic API Key", + "shortDescription" : { + "text" : "(?i)(?:key|api|token|secret|client|passwd|password|auth)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9a-z\\-_.=]{10,150})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)" + } + } ] + } + }, + "results" : [ { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/docker-compose.yml." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/docker-compose.yml" + }, + "region" : { + "startLine" : 12, + "startColumn" : 14, + "endLine" : 13, + "endColumn" : 1, + "snippet" : { + "text" : "531486b2bf646636a6a1bba61e78ec4a4a54efbd" + }, + "properties" : { + "secretscan.sereco.severity" : "high" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/src/api/application/config/database.php." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/src/api/application/config/database.php" + }, + "region" : { + "startLine" : 80, + "startColumn" : 7, + "endLine" : 80, + "endColumn" : 61, + "snippet" : { + "text" : "531486b2bf646636a6a1bba61e78ec4a4a54efbd" + }, + "properties" : { + "secretscan.sereco.severity" : "high" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/web/src/app/thunks/Authentication/ForgotPassword/handleForgotPasswordGetOTPThunk.tsx." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/web/src/app/thunks/Authentication/ForgotPassword/handleForgotPasswordGetOTPThunk.tsx" + }, + "region" : { + "startLine" : 32, + "startColumn" : 14, + "endLine" : 32, + "endColumn" : 56, + "snippet" : { + "text" : "9bbc0d79e686e847bc305c9bd4cc2ea6" + }, + "properties" : { + "secretscan.sereco.severity" : "high" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/Backend/web/src/app/thunks/OTP/handleGetOTPThunk.tsx." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/Backend/web/src/app/thunks/OTP/handleGetOTPThunk.tsx" + }, + "region" : { + "startLine" : 31, + "startColumn" : 14, + "endLine" : 31, + "endColumn" : 56, + "snippet" : { + "text" : "9bbc0d79e686e847bc305c9bd4cc2ea6" + }, + "properties" : { + "secretscan.sereco.severity" : "high" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/iOS/Source Code/Podfile.lock." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/iOS/Source Code/Podfile.lock" + }, + "region" : { + "startLine" : 23, + "startColumn" : 4, + "endLine" : 24, + "endColumn" : 1, + "snippet" : { + "text" : "b3816fddcf28aa29d94b10ec305cd52be14c472b" + }, + "properties" : { + "secretscan.sereco.severity" : "high" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + }, { + "ruleId" : "generic-api-key", + "message" : { + "text" : "generic-api-key has detected secret for file UnSAFE_Bank/iOS/Source Code/Pods/Manifest.lock." + }, + "locations" : [ { + "physicalLocation" : { + "artifactLocation" : { + "uri" : "UnSAFE_Bank/iOS/Source Code/Pods/Manifest.lock" + }, + "region" : { + "startLine" : 23, + "startColumn" : 4, + "endLine" : 24, + "endColumn" : 1, + "snippet" : { + "text" : "b3816fddcf28aa29d94b10ec305cd52be14c472b" + }, + "properties" : { + "secretscan.sereco.severity" : "high" + } + } + } + } ], + "partialFingerprints" : { + "commitSha" : "", + "email" : "", + "author" : "", + "date" : "", + "commitMessage" : "" + } + } ] + } ] +} \ No newline at end of file diff --git a/sechub-wrapper-xray/src/test/java/com/mercedesbenz/sechub/wrapper/xray/util/ZipFileExtractorTest.java b/sechub-wrapper-xray/src/test/java/com/mercedesbenz/sechub/wrapper/xray/util/ZipFileExtractorTest.java index 053d47c805..0108859a15 100644 --- a/sechub-wrapper-xray/src/test/java/com/mercedesbenz/sechub/wrapper/xray/util/ZipFileExtractorTest.java +++ b/sechub-wrapper-xray/src/test/java/com/mercedesbenz/sechub/wrapper/xray/util/ZipFileExtractorTest.java @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.wrapper.xray.util; -import static com.mercedesbenz.sechub.test.TestFileReader.loadTextFile; -import static com.mercedesbenz.sechub.test.TestUtil.createTempDirectoryInBuildFolder; +import static com.mercedesbenz.sechub.test.TestFileReader.*; +import static com.mercedesbenz.sechub.test.TestUtil.*; import static org.junit.jupiter.api.Assertions.*; import java.io.File; @@ -57,10 +57,10 @@ void unzipFile_with_zip_files() throws IOException, XrayWrapperReportException { assertFileExists(expectedFile03); assertFileExists(expectedFileSomeFile); - assertEquals("testfile01", loadTextFile(expectedFile01)); - assertEquals("testfile02", loadTextFile(expectedFile02)); - assertEquals("testfile03", loadTextFile(expectedFile03)); - assertEquals("somefile", loadTextFile(expectedFileSomeFile)); + assertEquals("testfile01", readTextFromFile(expectedFile01)); + assertEquals("testfile02", readTextFromFile(expectedFile02)); + assertEquals("testfile03", readTextFromFile(expectedFile03)); + assertEquals("somefile", readTextFromFile(expectedFileSomeFile)); } private void assertFileExists(Path path) { diff --git a/settings.gradle b/settings.gradle index bc71eea6b1..2b5c400749 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,8 @@ include 'sechub-cli', /* openapi */ 'sechub-openapi-java', +'sechub-openapi-java-client', +'sechub-api-java', // DEPRECATED /* commons */ 'sechub-commons-core', @@ -13,6 +15,9 @@ include 'sechub-cli', 'sechub-commons-pds', 'sechub-commons-archive', +/* encryption */ +'sechub-commons-encryption', + /* server POD area */ 'sechub-server', // executable spring boot jar 'sechub-server-core', // core project for test compile dependencies and more @@ -28,6 +33,9 @@ include 'sechub-cli', /* integration test*/ 'sechub-integrationtest', +/* Arch Unit */ +'sechub-archunit-test', + /* server parts */ 'sechub-schedule', 'sechub-scan', @@ -84,37 +92,29 @@ include 'sechub-cli', // Wrapper checkmarx 'sechub-wrapper-checkmarx', -// Wrapper Xray -'sechub-wrapper-xray', - // Wrapper prepare 'sechub-wrapper-prepare', -/* webui */ -'sechub-webui-solution' - -String buildStage = System.getProperty("sechub.build.stage") +// Wrapper Xray +'sechub-wrapper-xray', -if (buildStage!=null) { +// secret validator +'sechub-wrapper-secret-validator', - /* build stage properrty is set */ - if (buildStage.equals("api-necessary") || buildStage.equals("all") ){ +/* System Test */ +'sechub-systemtest', - include 'sechub-api-java', /* needs open api file to generate */ +/* PDS tools */ +'sechub-pds-tools', - /* system test - needs java api*/ - 'sechub-systemtest', +// Examples (are below 'sechub-examples' folder) +'sechub-examples:example-sechub-api-java', - /* pds tools - needs java api */ - 'sechub-pds-tools', +/* WebUI */ +'sechub-webui', +'sechub-webui-solution' - // Examples (are below 'sechub-examples' folder) - 'sechub-examples:example-sechub-api-java', - /* WebUI - needs the Java API */ - 'sechub-webui' - } -} buildCache { def useTempCache = System.getenv('SECHUB_BUILD_USE_TMP_CACHE')