diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..2cb75b7 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,48 @@ +on: + push: + branches: [main] + paths: + - 'Dockerfile*' + - '.github/workflows/docker.yml' + pull_request: + branches: [main] + paths: + - 'Dockerfile*' + - '.github/workflows/docker.yml' + schedule: + - cron: "30 3 * * 1" + workflow_dispatch: + +permissions: {} + +jobs: + hadolint: + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + matrix: + dockerfile: [Dockerfile, Dockerfile.goreleaser] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0 + with: + dockerfile: ${{ matrix.dockerfile }} + + image-scan: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: {} + steps: + - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: ghcr.io/publiccodeyml/open-catalog-api:main + # Alpine packages only. The Go modules in the binary are + # govulncheck's job: it knows whether the vulnerable code is + # reached, trivy would fail on every module CVE. + vuln-type: os + severity: HIGH,CRITICAL + ignore-unfixed: true + exit-code: 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fc41d..985e1e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 privately and which versions get fixes. - CI checks for the Helm chart with `helm lint` and `kubeconform`, and for GitHub Actions workflows with `actionlint`. +- CI checks for the Dockerfiles with `hadolint`, and a weekly `trivy` + scan of the published container image for Alpine package + vulnerabilities. - `entityType`, `entityId`, `type` and `actor` filters on `GET /v1/events`. - An `extraEnv` value on the Helm chart, for environment variables without a dedicated chart value. @@ -54,6 +57,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security +- The released container image runs as an unprivileged user instead of + root. - A JSON Patch could set `createdAt` on a software or a catalog, moving it in the listings and breaking the pagination cursors near it. - The database image used for local development is pinned by digest, diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser index 99ac3a2..b7d7fb9 100644 --- a/Dockerfile.goreleaser +++ b/Dockerfile.goreleaser @@ -9,4 +9,14 @@ COPY open-catalog-api /usr/local/bin/open-catalog-api RUN ln -s /usr/local/bin/open-catalog-api /usr/local/bin/developers-italia-api \ && ln -s /usr/local/bin/open-catalog-api /usr/local/bin/software-catalog-api +# adduser writes the build day into the password age field of +# /etc/shadow, which breaks the reproducible build. So undo its change. +RUN addgroup -S -g 10001 api \ + && adduser -S -D -H -G api -u 10001 -s /sbin/nologin api \ + && sed -i 's/^api:!:[0-9]*:/api:!:0:/' /etc/shadow + +# Numeric, so a pod with runAsNonRoot and no runAsUser starts: the +# kubelet refuses a user it cannot verify from a name. +USER 10001:10001 + ENTRYPOINT ["/usr/local/bin/open-catalog-api"]