diff --git a/.dockerfilelintrc b/.dockerfilelintrc index e691449..b56beef 100644 --- a/.dockerfilelintrc +++ b/.dockerfilelintrc @@ -2,4 +2,5 @@ rules: apt-get_missing_rm: off apt-get_recommends: off + apt-get-upgrade: off sudo_usage: off diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index ceb8cb9..e1f2776 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -14,6 +14,8 @@ on: env: REGISTRY: ghcr.io IMAGE_PATH: ghcr.io/${{ github.repository }} + TARFILE_NAME: image.tar + jobs: tests: @@ -25,41 +27,76 @@ jobs: - name: Static Analysis uses: pre-commit/action@v3.0.0 - - name: Build (but Don't Push) Docker Image - uses: docker/build-push-action@v5 + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4.1.1 + with: + images: ${{ env.IMAGE_PATH }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} # major.minor.patch + type=semver,pattern={{major}}.{{minor}} + + - name: Check that build works, save for scanning, but don't push yet + uses: docker/build-push-action@v6.4.0 with: context: . push: false + outputs: type=tar,dest=${{ env.TARFILE_NAME }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Upload tarball for use by Trivy job + uses: actions/upload-artifact@v4 + with: + name: ${{ env.TARFILE_NAME }} + path: ${{ env.TARFILE_NAME }} - build-and-push-image: + outputs: + meta_json: ${{ steps.meta.outputs.json }} + tarfile_artifact: ${{ env.TARFILE_NAME }} + + trivy-scan: + needs: tests + uses: "./.github/workflows/trivy.yml" + with: + SOURCE_TYPE: tar + IMAGE_NAME: image-name + TARFILE_NAME: ${{ needs.tests.outputs.tarfile_artifact }} + EXIT_CODE: 1 + + push-image: if: ${{ github.event_name == 'push' }} - needs: [tests] + needs: [tests, trivy-scan] runs-on: ubuntu-latest permissions: contents: read packages: write + strategy: + matrix: + value: ${{ fromJSON(needs.tests.outputs.meta_json).tags }} steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Download tar file + id: tar-download + uses: actions/download-artifact@v4 + with: + name: ${{ env.TARFILE_NAME }} + path: /tmp - - name: Log in to the Container registry - uses: docker/login-action@v2.1.0 + - name: Load Docker image from tar + run: cat + ${{ steps.tar-download.outputs.download-path}}/${{ env.TARFILE_NAME}} + | docker import - ${{ matrix.value }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4.1.1 - with: - images: ${{ env.IMAGE_PATH }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + - name: Push Docker image + run: docker push ${{ matrix.value }} +... diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 0000000..da967cb --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,90 @@ +--- +# +# This workflow runs Trivy on a Docker image +# It can pull the image from a container registry +# or download a tar file. The latter is used +# to check a container image prior to publishing +# to the registry. + +name: Run Trivy on a Docker image and push results to GitHub + +on: + workflow_call: + inputs: + SOURCE_TYPE: # 'tar' or 'image' + required: true + type: string + TARFILE_NAME: # only used if SOURCE_TYPE=='tar' + required: false + type: string + IMAGE_NAME: + required: true + type: string + EXIT_CODE: # return code for failed scan. 0 means OK + required: false + type: number + default: 0 + +env: + sarif_file_name: trivy-results.sarif + +jobs: + trivy: + name: Trivy + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download tar file + id: tar-download + uses: actions/download-artifact@v4 + if: ${{ inputs.SOURCE_TYPE == 'tar' }} + with: + name: ${{ inputs.TARFILE_NAME }} + path: /tmp + + - name: load docker image from tar file + if: ${{ inputs.SOURCE_TYPE == 'tar' }} + run: cat ${{ steps.tar-download.outputs.download-path + }}/${{ inputs.TARFILE_NAME + }} | docker import - ${{ inputs.IMAGE_NAME }} + + - name: Run Trivy vulnerability scanner for any major issues + uses: aquasecurity/trivy-action@0.24.0 + id: trivy + with: + image-ref: ${{ inputs.IMAGE_NAME }} + ignore-unfixed: true # skip vul'ns for which there is no fix + # list files to skip, each with a justification + skip-files: | + /usr/local/lib/R/site-library/gargle/extdata/fake_service_account.json + /usr/local/lib/R/site-library/openssl/doc/keys.html + # fake_service_account.json is a fake account that gets flagged as a credentials file + # keys.html is a documentation file that appears to contain cred's + severity: 'CRITICAL,HIGH' + format: 'sarif' + # only output findings for configured severities + limit-severities-for-sarif: true + output: ${{ env.sarif_file_name }} + exit-code: ${{ inputs.EXIT_CODE }} + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3.25.12 + # This is the recommended way to upload scan results + # after Trivy exits with HIGH/CRITICAL findings + # See https://github.com/aquasecurity/trivy-action?\ + # tab=readme-ov-file#using-trivy-with-github-code-scanning + # Note that here instead of using `always()` which would + # allow the step to run if *any* preceeding step failed, + # this logic ensures that the step ony runs if all steps + # succeed or if only the 'trivy' step fails. + if: ${{ success() || steps.trivy.conclusion=='failure' }} + with: + sarif_file: ${{ env.sarif_file_name }} + wait-for-processing: true +... diff --git a/.github/workflows/trivy_periodic_image_scan.yml b/.github/workflows/trivy_periodic_image_scan.yml new file mode 100644 index 0000000..ce18a9f --- /dev/null +++ b/.github/workflows/trivy_periodic_image_scan.yml @@ -0,0 +1,35 @@ +--- +# +# This workflow scans the published container images +# for new vulnerabilities daily, publishing findings. +# Findings will be associated with the 'main' branch +# of the repo' in the GitHub Security tab. +# +name: Trivy Periodic Image Scan + +on: + schedule: + # run daily + - cron: "0 0 * * *" + +jobs: + to-lower-case: + runs-on: ubuntu-latest + steps: + - name: Ensure image name is lower case + id: repo_name + uses: vishalmamidi/lowercase-action@v1 + with: + string: ghcr.io/${{ github.repository }}:main + outputs: + lowercase-repo-name: ${{ steps.repo_name.outputs.lowercase }} + + periodic-scan: + needs: lower-case + uses: "./.github/workflows/trivy.yml" + with: + SOURCE_TYPE: image + # While GitHub repo's can be mixed (upper and lower) case, + # Docker images can only be lower case + IMAGE_NAME: ${{ needs.to-lower-case.outputs.lowercase-repo-name }} +... diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..fb8ac81 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,12 @@ +# +# List vulnerabilities flagged by Trivy but for which +# the affected code is not used or the risk is acceptable. +# Enter the ID of the vulnerability along with the +# justification as comment, for example: +# +# # Accept the risk +# CVE-2018-14618 +# +# More here: +# https://aquasecurity.github.io/trivy/v0.22.0/vulnerability/examples/filter/ +# diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..5a6a292 --- /dev/null +++ b/.yamllint @@ -0,0 +1,7 @@ +extends: default + +rules: + # allow long lines, needed for long file paths + line-length: + max: 200 + level: error diff --git a/Dockerfile b/Dockerfile index f3bfcf7..2233b02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,10 @@ -FROM rocker/rstudio:4.3.2 +FROM rocker/rstudio:4.4.1 # no login required ENV DISABLE_AUTH=true RUN apt-get -y update && \ +apt-get -y upgrade && \ apt-get -y install libpng-dev \ python3 \ python3-pip \ diff --git a/README.md b/README.md index f04afcf..cff5de6 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,28 @@ To run: ``` -docker run -d -p 8787:8787 ghcr.io/sage-bionetworks-it/rstudio-service-catalog:v1.0.0 +docker run -d -p 8787:8787 ghcr.io/sage-bionetworks-it/rstudio-service-catalog:1.0.0 ``` + +## Versioning + +Semantic versioning is used and containers are tagged based on GitHub tags: If a tag, +1.2.3 is pushed to GitHub then a container image is built with tags `1.2.3` as well as `1.2`. +Thus the `major.minor` tag is overwritten when the repo' is patched. + + +## Security + +Trivy is run on each built container and they will not be published +to `ghcr.io` if any CRITICAL or HIGH +vulnerabilites are found. Trivy is also run daily to check for new +vulnerabilities in existing images. So periodic review of new findings +is needed: Go to the Security tab in GitHub, select Code Scanning at left, +and then select Branch > Main to check for new findings. To suppress +false positives, either: + +- Enter the CVE in `.trivyignore`, or + +- Enter the file to skip while scanning in the `trivy.yml` workflow. + +In either case, add a comment justifying why the finding is suppressed.