diff --git a/docs/codacy-api/examples/uploading-container-image-sboms.md b/docs/codacy-api/examples/uploading-container-image-sboms.md new file mode 100644 index 0000000000..34aa2d5e95 --- /dev/null +++ b/docs/codacy-api/examples/uploading-container-image-sboms.md @@ -0,0 +1,108 @@ +--- +description: Instructions on how to upload container image SBOMs to Codacy using the API, and how to list and delete image tags. +--- + +# Uploading container image SBOMs to Codacy + +Codacy scans your container images for known vulnerabilities from an SBOM (Software Bill of Materials) that you upload. If your pipeline already produces an SBOM, or you generate one with a tool other than the [Codacy CLI v2](https://github.com/codacy/codacy-cli-v2), you can upload it directly through the API and monitor the results as findings under [Container scanning](../../security/container-scanning.md). + +Before you automate this, read [how tagging affects your findings](../../security/container-scanning.md#how-tagging-affects-your-findings). The tag you upload to decides whether Codacy keeps one set of findings for the image or starts a new set with every release, and it's the choice that's hardest to reverse. + +## Uploading an SBOM to Codacy + +1. Generate an SBOM of your container image in CycloneDX or SPDX format, using the tool of your choice. + +1. Upload it to Codacy using the API endpoint [uploadImageSbom](https://app.codacy.com/api/api-docs#uploadimagesbom): + + ```bash + curl -X POST https://app.codacy.com/api/v3/organizations///image-sboms \ + -H 'api-token: ' \ + -H 'Content-Type: multipart/form-data' \ + -F 'sbom=@' \ + -F 'imageName=' \ + -F 'tag=' + ``` + + A successful upload returns `204 No Content`. The image is scanned on the next nightly run, not immediately. + +Replace the placeholders with your own values: + +- **API_KEY:** [Account API token](../api-tokens.md#account-api-tokens) used to authenticate on the Codacy API. +- **GIT_PROVIDER:** Git provider hosting the organization, using one of the values in the table below. For example, `gh` for GitHub Cloud. + + | Value | Git provider | + |-------|-----------------| + | `gh` | GitHub Cloud | + | `gl` | GitLab Cloud | + | `bb` | Bitbucket Cloud | + +- **ORGANIZATION:** Name of the organization on the Git provider. For example, `codacy`. +- **SBOM_PATH:** Path to the file containing the SBOM. +- **IMAGE_NAME:** Name of the container image, without a tag. For example, `myapp`. +- **TAG:** Tag to record the findings against. For example, `prod` or `1.4.2`. + +The form also accepts two optional fields: + +- **repositoryName:** Repository to associate the image with, so findings link back to it. +- **environment:** Environment where the image is deployed, such as `production`. It appears in the tag list. + +!!! important + The tag you send in the `tag` field must match the tag recorded inside the SBOM, or the upload is rejected with `400 Bad Request` and the message `SBOM tag mismatch: expected , found `. Tools differ in where they write the image reference: Trivy writes the full `name:tag` into the component name inside the SBOM, while some other generators write the name and the tag in different fields. Check what your generator produces before scripting this. + +## Listing the image tags you have uploaded + +To retrieve the tags Codacy holds for an image, use the endpoint [listImageTags](https://app.codacy.com/api/api-docs#listimagetags): + +```bash +curl -X GET 'https://app.codacy.com/api/v3/organizations///images//tags?limit=100' \ + -H 'api-token: ' \ + -H 'Accept: application/json' +``` + +The endpoint returns at most 100 tags per request and paginates with a cursor. See [using pagination](../using-the-codacy-api.md#using-pagination) to retrieve the remaining pages. + +## Deleting an image tag + +Your organization can hold 1,000 image tags in total. Once you reach that limit, Codacy stops accepting image tags it hasn't seen before, so a pipeline that uploads a new tag on every release stops being scanned. To make room, delete the tags for releases you no longer support using [deleteImageTag](https://app.codacy.com/api/api-docs#deleteimagetag): + +```bash +curl -X DELETE https://app.codacy.com/api/v3/organizations///image-sboms//tags/ \ + -H 'api-token: ' +``` + +Deleting a tag removes its scan history and findings. To delete an image and all its tags at once, call the same endpoint without the `/tags/` segment. + +!!! tip + To prune tags on a schedule rather than one at a time, the [Codacy Cloud CLI](../../codacy-cloud-cli/index.md#keep-latest) does this in one command: `codacy image gh my-org my-service --delete --keep-latest 10`. + +!!! important + When your organization is at the limit, the upload fails with the message `Organization has reached the maximum limit of 1000 image SBOMs`. The tags you already have keep being scanned every day, so nothing else reports the problem. Match on the message rather than the status code, which is being made more specific. + +## Example: uploading an SBOM from your pipeline + +Use this example Bash script to upload an image SBOM to Codacy. This example can be adapted to fit your specific needs. + +The example script: + +1. Defines the [API token](../api-tokens.md#account-api-tokens) used to authenticate on the Codacy API. +1. Defines the image name and the tag to record the findings against. +1. Calls the endpoint [uploadImageSbom](https://app.codacy.com/api/api-docs#uploadimagesbom) to upload the SBOM. + +```bash +CODACY_API_TOKEN="" +GIT_PROVIDER="" # e.g., gh for GitHub +ORGANIZATION_NAME="" +IMAGE_NAME="" # e.g., myapp +IMAGE_TAG="prod" # one tag, reused every release +SBOM_FILE_PATH="sbom.cyclonedx.json" + +curl -X POST "https://app.codacy.com/api/v3/organizations/$GIT_PROVIDER/$ORGANIZATION_NAME/image-sboms" \ + -H "api-token: $CODACY_API_TOKEN" \ + -H "Content-Type: multipart/form-data" \ + -F "sbom=@$SBOM_FILE_PATH" \ + -F "imageName=$IMAGE_NAME" \ + -F "tag=$IMAGE_TAG" \ + -F "environment=production" +``` + +To record a separate set of findings per release, set `IMAGE_TAG` to the version your pipeline built instead, and delete the tags you no longer support so you stay under your organization limit. diff --git a/docs/codacy-cloud-cli/index.md b/docs/codacy-cloud-cli/index.md index daa88d2d16..349bd23df5 100644 --- a/docs/codacy-cloud-cli/index.md +++ b/docs/codacy-cloud-cli/index.md @@ -168,6 +168,67 @@ This information is also included when using `--output json`. !!! note Not every advisory lists specific affected functions — this section only appears when Codacy has identified them. +### Manage container images {: id="manage-container-images"} + +List the container images with SBOMs uploaded to an organization, inspect an image's tags, upload an SBOM, and delete tags you no longer need. These commands require an [account API token](../codacy-api/api-tokens.md#account-api-tokens), because they read organization-level data. + +!!! note + Available from Codacy Cloud CLI 1.12.1. + +```bash +# List the images in an organization, with their latest tag +codacy images gh my-org + +# List an image's tags, with environment, repository, and analysis dates +codacy image gh my-org my-service + +# Show a single tag +codacy image gh my-org my-service --tag 1.2.3 +``` + +Both commands return 100 results by default. Use `--limit` to raise that, up to 1000. + +Upload an SBOM your pipeline already produced, in SPDX or CycloneDX format: + +```bash +codacy image gh my-org my-service --tag 1.2.3 --upload ./sbom.json + +# Record where the image runs and which repository it belongs to +codacy image gh my-org my-service --tag prod --upload ./sbom.json \ + --environment production --repository my-repo +``` + +The file is checked before the request, so a wrong path or an empty file fails immediately. + +Delete a single tag, or the whole image: + +```bash +# One tag +codacy image gh my-org my-service --tag 1.2.3 --delete + +# Every tag of the image +codacy image gh my-org my-service --delete +``` + +### Keep container image tags under the organization limit {: id="keep-latest"} + +An organization can hold 1000 image tags in total. Once it reaches that limit, Codacy stops accepting image tags it hasn't seen before, so a pipeline that uploads a new tag on every release stops being scanned. See [how tagging affects your findings](../security/container-scanning.md#how-tagging-affects-your-findings). + +`--keep-latest` deletes the older tags of an image, keeping the most recently uploaded ones. Run it as the cleanup step of a release pipeline, **before** the upload, so that the space is freed before the new tag needs it: + +```bash +# Show what would be deleted, delete nothing +codacy image gh my-org my-service --delete --keep-latest 10 --dry-run + +# Delete, without prompting — for CI +codacy image gh my-org my-service --delete --keep-latest 10 --skip-confirmation +``` + +Deletes run one at a time and continue past failures, so a partial cleanup still frees space. Under `--output json` the command emits a single object listing the tags in `deleted` and the ones that failed in `failures`, and exits non-zero if any tag failed. + +!!! important + The organization limit counts image-and-tag pairs, while `--keep-latest` applies per image. Keeping 10 tags across 212 images is 2120 tags against a limit of 1000. The command warns when the number you ask for would exceed the limit across every image in the organization, and says what the limit allows per image instead. + ### List and filter pull requests ```bash diff --git a/docs/security/container-scanning.md b/docs/security/container-scanning.md index e278f3f2f0..c41d96eb5b 100644 --- a/docs/security/container-scanning.md +++ b/docs/security/container-scanning.md @@ -22,6 +22,77 @@ The security tool analyzes your uploaded SBOM (Software Bill of Materials) files No manual action is required to trigger scans after the initial setup. +## How tagging affects your findings + +Codacy keeps a separate set of findings for every image tag you upload, and scans each of those tags once a day. The tag you upload to therefore decides how your findings behave over time, and it's the setup choice that's hardest to reverse: findings you have already accumulated stay on the tags that produced them. + +You have two options: + +- **One list, kept up to date.** Upload every build to the same tag, such as `prod`. Each upload replaces the last, so vulnerabilities you fix close automatically and your dismissals and owners carry over. [See the pipeline example](#example-one-list). +- **A separate list per release.** Upload each build to a new tag, such as `1.4.2`. Every release starts its own set of findings, so a vulnerability you fixed in your newest release stays open on the older ones. [See the pipeline example](#example-per-release). + +This changes what Codacy scans, not what you deploy. You can keep deploying from an immutable tag or a digest while pointing Codacy at a single rolling tag. + +| | One list, kept up to date | A separate list per release | +| --- | --- | --- | +| Findings across releases | The same findings are updated | A new set every release | +| A vulnerability you fixed | Closes automatically | Stays open on the older tags | +| Dismissals and owners | Carry over to the next upload | Reset on every release | +| SLA and MTTR | Measured across the life of the image | Restart with every release | +| Which build introduced a vulnerability | Not recorded | Recorded exactly | +| Image tags stored | One per image | One per release | + +Upload to a single tag unless you need a per-release record of what shipped. + +### What the image tag limit does + +Your organization can hold 1,000 image tags in total, counted across all your images. Uploading again to a tag Codacy already holds doesn't count against the limit, so an organization that uploads every image to a single rolling tag can stay under it indefinitely. + +Once you reach the limit, Codacy stops accepting image tags it hasn't seen before. The tags you already have keep being scanned every day, so the images list still looks healthy, but the release you just shipped isn't scanned at all. The only signal is the error returned to your pipeline. + +To make room, delete the image tags for releases you no longer support. You can delete a single tag from the tag list of an image, or delete an image to remove all its tags at once. To prune tags automatically as part of a pipeline, use the [Codacy Cloud CLI](../codacy-cloud-cli/index.md#keep-latest). + +### Reducing findings on an image that already has many tags + +Changing the tag your pipeline uploads to doesn't change the findings you already have. The tags you accumulated keep their own findings, and Codacy keeps scanning every one of them every night, so the finding count stays put until you remove the tags behind it. + +To bring an image back under control: + +1. Point your pipeline at a single tag, following [one list, kept up to date](#how-tagging-affects-your-findings). New releases now update one set of findings instead of starting another. + +1. List the tags Codacy holds for the image, so you know what you are working with. The [Codacy Cloud CLI](../codacy-cloud-cli/index.md#manage-container-images) needs an [account API token](../codacy-api/api-tokens.md#account-api-tokens), from `codacy login` or the `CODACY_API_TOKEN` environment variable: + + ```bash + export CODACY_API_TOKEN="" + + codacy image gh "${ORGANIZATION_NAME}" "${IMAGE_NAME}" --limit 1000 + ``` + +1. Check what a cleanup would remove, without removing anything: + + ```bash + codacy image gh "${ORGANIZATION_NAME}" "${IMAGE_NAME}" \ + --delete --keep-latest 10 --dry-run + ``` + +1. Delete the tags for releases you no longer support: + + ```bash + codacy image gh "${ORGANIZATION_NAME}" "${IMAGE_NAME}" \ + --delete --keep-latest 10 + ``` + +Deleting a tag deletes the findings recorded against it, along with its scan history. The finding counts drop once the deletions have been processed, which happens shortly after the command returns rather than immediately. Keep the tags for the releases you still run in production, since those are the findings that describe something you are actually exposed to. + +!!! important + Deleting an image tag can't be undone. Start with `--dry-run`, and keep the tags for every release you still support. + +Tags accumulate per image, so find the images responsible before you start. Usually a small number of them account for most of the tags: + +```bash +codacy images gh "${ORGANIZATION_NAME}" +``` + ## Container scanning setup You can set up container scanning in one of two ways: by connecting your CI/CD pipeline or by manually uploading your image SBOM. Once configured, your image dependencies are scanned daily and results will appear in the Image card list. @@ -38,12 +109,123 @@ In order to do that, you need to: When CI/CD is configured: -- Images pushed through your pipeline are automatically detected -- New tags are picked up as they're published -- Scans are scheduled automatically +- Every pipeline run uploads an SBOM for the image and tag you name in the command +- Codacy scans each image tag you have uploaded once per day +- No manual action is required between releases This is the recommended setup for continuous coverage. +#### Example pipeline steps + +These examples use the [Codacy CLI v2](https://github.com/codacy/codacy-cli-v2), which generates the SBOM from your image and uploads it in a single command. They assume you have already set `CODACY_API_TOKEN` as described above. + +!!! note + Already generating SBOMs with another tool? [Upload them via the API.](../codacy-api/examples/uploading-container-image-sboms.md) + +Install the CLI once per pipeline run: + +```bash +curl -fsSL https://raw.githubusercontent.com/codacy/codacy-cli-v2/main/codacy-cli.sh -o codacy-cli.sh +chmod +x codacy-cli.sh +./codacy-cli.sh init +``` + +`upload-sbom` reads a Codacy configuration from the working directory, which is what `init` creates. Without it the upload stops with `No configuration file was found, execute init command first.` + +#### Example: one list, kept up to date {: id="example-one-list"} + +For [one list of findings, kept up to date](#how-tagging-affects-your-findings), point your rolling tag at the image you just built and upload that: + +```bash +docker tag "${IMAGE_NAME}:${IMAGE_VERSION}" "${IMAGE_NAME}:prod" + +./codacy-cli.sh upload-sbom \ + -a "${CODACY_API_TOKEN}" \ + -p gh \ + -o "${ORGANIZATION_NAME}" \ + -r "${REPOSITORY_NAME}" \ + -e production \ + "${IMAGE_NAME}:prod" +``` + +The `docker tag` alias stays on the build machine and is never pushed. Without it the CLI resolves `prod` against your registry and scans whatever that tag points at there, rather than the image your pipeline just built. + +#### Example: a separate list per release {: id="example-per-release"} + +For [a separate list per release](#how-tagging-affects-your-findings), delete the tags you no longer need and then upload the release tag: + +```bash +npm install -g "@codacy/codacy-cloud-cli" + +# Keep the 10 most recently uploaded tags, delete the rest +codacy image gh "${ORGANIZATION_NAME}" "${IMAGE_NAME}" \ + --delete --keep-latest 10 --skip-confirmation + +./codacy-cli.sh upload-sbom \ + -a "${CODACY_API_TOKEN}" \ + -p gh \ + -o "${ORGANIZATION_NAME}" \ + -r "${REPOSITORY_NAME}" \ + -e production \ + "${IMAGE_NAME}:${IMAGE_VERSION}" +``` + +Every release adds an image tag, so clean up on every run to stay under your organization limit. Put the cleanup **before** the upload: at the limit the upload is rejected, so a pipeline that uploads first and cleans up later can't make progress. Add `--dry-run` to see which tags would go without deleting anything. + +The [Codacy Cloud CLI](../codacy-cloud-cli/index.md#keep-latest) reads the same `CODACY_API_TOKEN` you set in step 1, and needs version 1.12.1 or later. + +Both examples use these placeholders. Replace them with your own values: + +- **CODACY_API_TOKEN:** [Account API token](../codacy-api/api-tokens.md#account-api-tokens) used to authenticate on Codacy, set as described in step 1 of the setup page. +- **`-p`:** Git provider hosting the organization, using one of the values in the table below. For example, `gh` for GitHub Cloud. + + | Value | Git provider | + |-------|-----------------| + | `gh` | GitHub Cloud | + | `gl` | GitLab Cloud | + | `bb` | Bitbucket Cloud | + +- **ORGANIZATION_NAME:** Name of the organization on the Git provider. For example, `codacy`. +- **IMAGE_NAME:** Name of the container image, without a tag. For example, `myapp`. +- **IMAGE_VERSION:** The tag your pipeline built, such as `1.4.2`. Only used by the per-release example. +- **REPOSITORY_NAME:** Optional. Name of the repository to associate the image with, so findings link back to it. +- **`-e`:** Optional. Environment where the image is deployed, such as `production`. It appears in the tag list. + +#### If your pipeline already produces an SBOM + +The examples above use the Codacy CLI v2 because it generates the SBOM and uploads it in one step. If your pipeline already produces an SBOM in CycloneDX or SPDX format, upload that file directly with the [Codacy Cloud CLI](../codacy-cloud-cli/index.md#manage-container-images) instead. It reads the same `CODACY_API_TOKEN` you set in step 1, and needs version 1.12.1 or later. + +```bash +npm install -g "@codacy/codacy-cloud-cli" + +# Uses the CODACY_API_TOKEN you set in step 1. +``` + +To keep one list of findings, upload every release to the same tag: + +```bash +codacy image gh "${ORGANIZATION_NAME}" "${IMAGE_NAME}" \ + --tag prod \ + --upload "${SBOM_FILE}" \ + --environment production \ + --repository "${REPOSITORY_NAME}" +``` + +To keep a separate list per release, clean up first and then upload under the release tag: + +```bash +codacy image gh "${ORGANIZATION_NAME}" "${IMAGE_NAME}" \ + --delete --keep-latest 10 --skip-confirmation + +codacy image gh "${ORGANIZATION_NAME}" "${IMAGE_NAME}" \ + --tag "${IMAGE_VERSION}" \ + --upload "${SBOM_FILE}" \ + --environment production \ + --repository "${REPOSITORY_NAME}" +``` + +This path never resolves the image itself, so it needs no `docker tag` alias: it attaches the SBOM file you name to the tag you name. + ### Manual upload You can also manually upload your container's Software Bill of Materials (SBOM) in CycloneDX or SPDX format. @@ -55,6 +237,8 @@ To manually upload an image SBOM, you need to: 2. Add the image tag; 3. Upload your SBOM file (environment and repository fields are optional). +Reuse a tag and each upload replaces the last. A new tag starts its own set of findings, so choose it with care: see [How tagging affects your findings](#how-tagging-affects-your-findings). + !!! note You can use the [Codacy CLI v2](https://github.com/codacy/codacy-cli-v2) to generate and upload your SBOM file to Codacy. @@ -86,8 +270,9 @@ For the image tags, the list is sorted by latest uploaded, and the information i Once a tag is scanned, you can click on the `check findings` link to access the findings page filtered by the respective results. !!! important - Findings are tied to specific image tags. To resolve a finding, "bump" the tag to a newer version if a fixed version exists (if not, a downgrade or an alternative image may be required). - For dynamic tags such as `latest`, Codacy will automatically close findings that are no longer present in the current analysis. If you use static tags, you will need to delete tags that are no longer used, as there's a limit of 1000 tags per organization. + Findings are tied to a specific image tag. To resolve one, upload an image where the dependency is fixed, usually by bumping it to a newer version. Where no fix exists, a downgrade or an alternative base image may be required. + +Whether that fix also closes the finding on your other tags depends on how you tag your images. See [How tagging affects your findings](#how-tagging-affects-your-findings). ## Deleting container image files from Codacy diff --git a/mkdocs.yml b/mkdocs.yml index 21eb964219..c136116800 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -774,6 +774,7 @@ nav: - codacy-api/examples/obtaining-current-issues-in-repositories.md - codacy-api/examples/identifying-commits-without-coverage-data.md - codacy-api/examples/uploading-dast-results.md + - codacy-api/examples/uploading-container-image-sboms.md - codacy-api/examples/triggering-dast-scans.md - Codacy CLIs: - codacy-analysis-cli/index.md