diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f04868ed..ee4bd751 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,117 +7,133 @@ permissions: {} on: push: tags: - - 'v*' - + - 'v*' + branches: + - report-server-fips jobs: - goreleaser: + release-reports-server: permissions: - contents: write - id-token: write + contents: read packages: write - pull-requests: write - outputs: - hashes: ${{ steps.hash.outputs.hashes }} - image: ${{ steps.digest.outputs.image }} - digest: ${{ steps.digest.outputs.digest }} - runs-on: ubuntu-latest - steps: - - name: Free disk space - uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - with: - tool-cache: true - android: true - dotnet: true - haskell: true - large-packages: false - docker-images: true - swap-storage: false - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - fetch-depth: 0 - - name: Fetch all tags - run: | - set -e - git fetch --force --tags - - name: Set up Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 - with: - go-version-file: go.mod - cache-dependency-path: go.sum - - name: Install Cosign - uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 - - name: Install Syft - uses: anchore/sbom-action/download-syft@b6a39da80722a2cb0ef5d197531764a89b5d48c3 # v0.15.8 - - name: Install Ko - uses: ko-build/setup-ko@ace48d793556083a76f1e3e6068850c1f4a369aa # v0.6 - - name: Run GoReleaser - id: goreleaser - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 - with: - distribution: goreleaser - version: latest - args: release --clean --timeout 90m - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload artifacts.json - uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # 2.9.0 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: dist/artifacts.json - asset_name: artifacts.json - tag: ${{ github.ref }} - - name: Upload metadata.json - uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # 2.9.0 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: dist/metadata.json - asset_name: metadata.json - tag: ${{ github.ref }} - - name: Generate subject - id: hash - env: - ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" - run: | - set -euo pipefail - checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path') - hashes=$(cat $checksum_file | base64 -w0) - echo "hashes=$hashes" >> $GITHUB_OUTPUT - - name: Image digest - id: digest - env: - ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" - run: | - set -euo pipefail - image_and_digest=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Docker Manifest") | .path') - image=$(echo "${image_and_digest}" | cut -d'@' -f1 | cut -d':' -f1) - digest=$(echo "${image_and_digest}" | cut -d'@' -f2) - echo "image=$image" >> "$GITHUB_OUTPUT" - echo "digest=$digest" >> "$GITHUB_OUTPUT" - - # provenance: - # needs: - # - goreleaser + id-token: write + uses: ./.github/workflows/reuse.yaml + with: + publish_command: docker-publish-reports-server-fips + digest_command: docker-get-reports-server-digest + image_name: reports-server-fips + tag: release + main: ./ + secrets: + registry_username: ${{ github.actor }} + registry_password: ${{ secrets.GITHUB_TOKEN }} + # goreleaser: # permissions: - # actions: read - # id-token: write # contents: write - # uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0 - # with: - # base64-subjects: "${{ needs.goreleaser.outputs.hashes }}" - # upload-assets: true - - # image-provenance: - # needs: - # - goreleaser - # permissions: - # actions: read # id-token: write # packages: write - # uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0 - # with: - # image: ${{ needs.goreleaser.outputs.image }} - # digest: ${{ needs.goreleaser.outputs.digest }} - # registry-username: ${{ github.actor }} - # secrets: - # registry-password: ${{ secrets.GITHUB_TOKEN }} + # pull-requests: write + # outputs: + # hashes: ${{ steps.hash.outputs.hashes }} + # image: ${{ steps.digest.outputs.image }} + # digest: ${{ steps.digest.outputs.digest }} + # runs-on: ubuntu-latest + # steps: + # - name: Free disk space + # uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + # with: + # tool-cache: true + # android: true + # dotnet: true + # haskell: true + # large-packages: false + # docker-images: true + # swap-storage: false + # - name: Checkout + # uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + # with: + # fetch-depth: 0 + # - name: Fetch all tags + # run: | + # set -e + # git fetch --force --tags + # - name: Set up Go + # uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + # with: + # go-version-file: go.mod + # cache-dependency-path: go.sum + # - name: Install Cosign + # uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0 + # - name: Install Syft + # uses: anchore/sbom-action/download-syft@b6a39da80722a2cb0ef5d197531764a89b5d48c3 # v0.15.8 + # - name: Install Ko + # uses: ko-build/setup-ko@ace48d793556083a76f1e3e6068850c1f4a369aa # v0.6 + # - name: Run GoReleaser + # id: goreleaser + # uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + # with: + # distribution: goreleaser + # version: latest + # args: release --clean --timeout 90m + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - name: Upload artifacts.json + # uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # 2.9.0 + # with: + # repo_token: ${{ secrets.GITHUB_TOKEN }} + # file: dist/artifacts.json + # asset_name: artifacts.json + # tag: ${{ github.ref }} + # - name: Upload metadata.json + # uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # 2.9.0 + # with: + # repo_token: ${{ secrets.GITHUB_TOKEN }} + # file: dist/metadata.json + # asset_name: metadata.json + # tag: ${{ github.ref }} + # - name: Generate subject + # id: hash + # env: + # ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" + # run: | + # set -euo pipefail + # checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path') + # hashes=$(cat $checksum_file | base64 -w0) + # echo "hashes=$hashes" >> $GITHUB_OUTPUT + # - name: Image digest + # id: digest + # env: + # ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" + # run: | + # set -euo pipefail + # image_and_digest=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Docker Manifest") | .path') + # image=$(echo "${image_and_digest}" | cut -d'@' -f1 | cut -d':' -f1) + # digest=$(echo "${image_and_digest}" | cut -d'@' -f2) + # echo "image=$image" >> "$GITHUB_OUTPUT" + # echo "digest=$digest" >> "$GITHUB_OUTPUT" + + # # provenance: + # # needs: + # # - goreleaser + # # permissions: + # # actions: read + # # id-token: write + # # contents: write + # # uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0 + # # with: + # # base64-subjects: "${{ needs.goreleaser.outputs.hashes }}" + # # upload-assets: true + + # # image-provenance: + # # needs: + # # - goreleaser + # # permissions: + # # actions: read + # # id-token: write + # # packages: write + # # uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0 + # # with: + # # image: ${{ needs.goreleaser.outputs.image }} + # # digest: ${{ needs.goreleaser.outputs.digest }} + # # registry-username: ${{ github.actor }} + # # secrets: + # # registry-password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reuse.yaml b/.github/workflows/reuse.yaml new file mode 100644 index 00000000..e008e017 --- /dev/null +++ b/.github/workflows/reuse.yaml @@ -0,0 +1,184 @@ +name: Create Publish and Sign Docker Image for FIPS Compliance +on: + workflow_call: + inputs: + publish_command: + required: true + type: string + digest_command: + required: true + type: string + image_name: + required: true + type: string + tag: + required: true + type: string + main: + type: string + secrets: + registry_username: + required: true + registry_password: + required: true +jobs: + build: + runs-on: x1 + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Checkout release + if: ${{ inputs.tag == 'release'}} + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + with: + fetch-depth: 0 + + - name: Checkout image + if: ${{ inputs.tag == 'image'}} + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + + - name: Unshallow + if: ${{ inputs.tag == 'image'}} + run: git fetch --prune --unshallow --tags + + - name: Set up Go + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: ~1.23.5 + + - name: Install Cosign + uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 + + - name: Log into ghcr.io + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@27d0a4f181a40b142cce983c5393082c365d1480 # v1.2.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 #v3.7.1 + id: buildx + with: + install: true + + - name: Run Trivy vulnerability scanner in repo mode + if: ${{inputs.tag == 'release'}} + uses: aquasecurity/trivy-action@40c4ca9e7421287d0c5576712fdff370978f9c3c + with: + scan-type: 'fs' + ignore-unfixed: true + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + + - name: Set Version + if: ${{ inputs.tag == 'release'}} + run: | + echo "REPORTS_SERVER_VERSION=$(git describe --match "v[0-9]*" --tags $(git rev-list --tags --max-count=1))" >> $GITHUB_ENV + - name: Generate SBOM JSON + if: ${{inputs.tag == 'release'}} + uses: CycloneDX/gh-gomod-generate-sbom@c18e41a4e3defe6dbf69b594e4d831a89db82ead # v1.0.0 + with: + version: v1 + args: app -licenses -json -output ${{inputs.image_name}}-${{ env.REPORTS_SERVER_VERSION }}-bom.cdx.json -main ${{inputs.main}} + + - name: Upload SBOM JSON + if: ${{inputs.tag == 'release'}} + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: ${{inputs.image_name}}-bom-cdx + path: ${{inputs.image_name}}-v*-bom.cdx.json + + - name: Extract branch name + if: ${{inputs.tag == 'image'}} + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + id: extract_branch + + - name: Check branch + if: ${{inputs.tag == 'image' && steps.extract_branch.outputs.branch != 'main'}} + id: check-branch + run: | + if [[ ${{ steps.extract_branch.outputs.branch }} =~ ^release-[0-9]+\.[0-9]$ ]]; then + echo ::set-output name=match::true + fi + - name: Debug Inputs + run: | + echo "Tag: ${{ inputs.tag }}" + echo "publish_command : ${{ inputs.publish_command}}" + echo "Digest Command: ${{ inputs.digest_command }}" + echo "Image Name: ${{ inputs.image_name }}" + echo "Repository: ${{ github.repository }}" + echo "Workflow: ${{ github.workflow }}" + echo "SHA: ${{ github.sha }}" + echo "secrets.GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" + echo "github.actor: ${{ github.actor }}" + echo "Branch: ${{ steps.extract_branch.outputs.branch }}" + - name: Docker images publish + if: ${{inputs.tag == 'image' && steps.extract_branch.outputs.branch == 'main'}} + run: make ${{inputs.publish_command}} FIPS_ENABLED=1 + + - name: get image digest + if: ${{inputs.tag == 'image' && steps.extract_branch.outputs.branch == 'main'}} + id: get-step-image + run: | + digest=$(make ${{inputs.digest_command}} FIPS_ENABLED=1) + echo "digest=${digest}" >> $GITHUB_ENV + - name: Docker release-images publish + if: ${{inputs.tag == 'release' || inputs.tag == 'image' }} + run: make ${{inputs.publish_command}} FIPS_ENABLED=1 + + - name: Clear Sigstore TUF Cache + run: | + rm -rf ~/.sigstore + - name: Get release-image digest + if: ${{ inputs.tag == 'release' || (inputs.tag == 'image' && steps.check-branch.outputs.match == 'true') }} + id: get-step + run: | + digest=$(make ${{inputs.digest_command}} FIPS_ENABLED=1 2>/dev/null || true) + if [[ -z "$digest" ]]; then + echo "Error: Unable to generate digest. Ensure the repository exists and credentials are valid." >&2 + exit 1 + fi + echo "digest=$digest" >> $GITHUB_ENV + echo "Digest: $digest" + - name: Debug Digest + run: | + echo "Digest: ${{ env.digest }}" + if [[ -z "${{ env.digest }}" || "${{ env.digest }}" == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ]]; then + echo "Error: Digest is empty or invalid." >&2 + exit 1 + fi + - name: Sign image + if: ${{ inputs.tag == 'image' && steps.extract_branch.outputs.branch == 'main' }} + env: + COSIGN_EXPERIMENTAL: "true" + COSIGN_REPOSITORY: "ghcr.io/${{ github.repository_owner }}/${{ github.repository }}/signatures" + run: | + cosign sign --yes \ + -a "repo=${{ github.repository }}" \ + -a "workflow=${{ github.workflow }}" \ + -a "ref=${{ github.sha }}" \ + ghcr.io/${{ github.repository_owner }}/${{ inputs.image_name }}@sha256:${digest} + - name: Sign release-image + if: ${{ inputs.tag == 'release' || (inputs.tag == 'image' && steps.check-branch.outputs.match == 'true') }} + env: + COSIGN_EXPERIMENTAL: "true" + COSIGN_REPOSITORY: "ghcr.io/${{ github.repository_owner }}/${{ github.repository }}/signatures" + digest: ${{ env.digest }} + run: | + echo "Signing with digest: $digest" + cosign sign --yes \ + -a "repo=${{ github.repository }}" \ + -a "workflow=${{ github.workflow }}" \ + -a "ref=${{ github.sha }}" \ + ghcr.io/${{ github.repository_owner }}/${{ inputs.image_name }}@sha256:$digest + - name: Attach SBOM + if: ${{inputs.tag == 'release'}} + env: + COSIGN_REPOSITORY: "ghcr.io/${{ github.repository_owner }}/${{ github.repository }}/sbom" + run: cosign attach sbom --sbom ./${{inputs.image_name}}-v*-bom.cdx.json --type cyclonedx ghcr.io/${{ github.repository_owner }}/${{inputs.image_name}}@sha256:${{ env.digest }} diff --git a/Dockerfile.fips b/Dockerfile.fips new file mode 100644 index 00000000..989c7d86 --- /dev/null +++ b/Dockerfile.fips @@ -0,0 +1,42 @@ +FROM mcr.microsoft.com/oss/go/microsoft/golang:1.23.4-fips-cbl-mariner2.0 AS builder + +ENV GOPATH=/go \ + PATH=/usr/local/go/bin:/go/bin:/usr/local/bin:/usr/bin:$PATH \ + CGO_ENABLED=1 \ + FIPS_ENABLED=1 + +RUN mkdir -p /go && \ + tdnf install -y \ + ca-certificates \ + build-essential \ + shadow-utils && \ + tdnf clean all + +WORKDIR /app +COPY . . + +ARG LD_FLAGS + +ARG TARGETARCH +RUN GOOS=linux GOARCH=$TARGETARCH \ + BUILD_TAGS=fips GOEXPERIMENT=systemcrypto \ + CGO_ENABLED=1 FIPS_ENABLED=1 \ + go build -ldflags="-s -w" -o /app/reports-server ./ + +RUN groupadd --system appgroup && \ + useradd --system --uid 1001 --gid appgroup --home-dir /nonexistent --shell /usr/sbin/nologin appuser && \ + chown appuser:appgroup /app/reports-server + +FROM mcr.microsoft.com/cbl-mariner/distroless/base:2.0-nonroot + +COPY --from=builder /etc/passwd /etc/passwd + +COPY --from=builder /etc/group /etc/group + +COPY --from=builder /app/reports-server /reports-server + +COPY --from=builder /etc/ssl/certs /etc/ssl/certs + +USER 1001 + +ENTRYPOINT ["/reports-server"] diff --git a/Makefile b/Makefile index 31ff2ca2..2f72e1a5 100644 --- a/Makefile +++ b/Makefile @@ -289,3 +289,46 @@ ko-login: $(KO) ko-publish-reports-server: ko-login ## Build and publish reports-server image (with ko) @LD_FLAGS=$(LD_FLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(REPO_REPORTS_SERVER) \ $(KO) build . --bare --tags=$(KO_TAGS) --platform=$(PLATFORMS) + + +################################## +# FIPS VARIABLES +################################## +FIPS_ENABLED := 0 # Default to FIPS disabled + +ifeq ($(FIPS_ENABLED), 1) +IMAGE_TAG := $(shell git describe --tags --abbrev=0) +LD_FLAGS :="-s -w" +endif + +REPORTS_SERVER_FIPS := reports-server-fips +REPO_REPORTS_SERVER_FIPS := $(REGISTRY)/$(ORG)/$(REPORTS_SERVER_FIPS) + +################################## +# REPORTS-SERVER FIPS CONTAINER +################################## + +.PHONY: docker-build-and-push-reports-server-fips +docker-buildx-builder: + if ! docker buildx ls | grep -q reports-server-fips; then \ + docker buildx create --name reports-server-fips --use; \ + else \ + docker buildx use reports-server-fips; \ + fi + +reports-server-fips: fmt vet + GOOS=linux GOARCH=amd64 CGO_ENABLED=$(CGO_ENABLED) go build ./ -o $(PWD)/$(REPO_REPORTS_SERVER_FIPS) -tags "$(BUILD_TAGS)" -ldflags="$(LD_FLAGS)" $(PWD)/ + +docker-publish-reports-server-fips: docker-buildx-builder docker-build-and-push-reports-server-fips + +docker-build-and-push-reports-server-fips: docker-buildx-builder + @docker buildx build --file $(PWD)/Dockerfile.fips \ + --progress plain \ + --platform linux/amd64,linux/arm64 \ + --tag $(REPO_REPORTS_SERVER_FIPS):$(IMAGE_TAG) \ + . \ + --build-arg LD_FLAGS=$(LD_FLAGS) \ + --push + +docker-get-reports-server-digest: + @docker buildx imagetools inspect --raw $(REPO_REPORTS_SERVER_FIPS):$(IMAGE_TAG) | perl -pe 'chomp if eof' | openssl dgst -sha256 | sed 's/^.* //' diff --git a/charts/reports-server/templates/deployment.yaml b/charts/reports-server/templates/deployment.yaml index ab481d56..6890d14a 100644 --- a/charts/reports-server/templates/deployment.yaml +++ b/charts/reports-server/templates/deployment.yaml @@ -86,6 +86,15 @@ spec: volumeMounts: - mountPath: /tmp name: tmp-dir + {{- $tag := default .Values.image.tag .Chart.AppVersion -}} + {{- $imageRegistry := default .Values.image.registry "" -}} + {{- $repository := required "An image repository is required" .Values.image.repository -}} + {{- $fipsEnabled := .Values.fipsEnabled | default false -}} + {{- if $fipsEnabled -}} + {{ $imageRegistry }}/{{ $repository }}-fips:{{ $tag }} + {{- else -}} + {{ $imageRegistry }}/{{ $repository }}:{{ $tag }} + {{- end -}} image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: diff --git a/charts/reports-server/values.yaml b/charts/reports-server/values.yaml index 5947589f..a05970f5 100644 --- a/charts/reports-server/values.yaml +++ b/charts/reports-server/values.yaml @@ -2,7 +2,7 @@ # @ignored templating: enabled: false - +fipsEnabled: false cloudnative-pg: crds: create: false @@ -224,12 +224,13 @@ apiServicesManagement: image: # -- (string) Image registry - registry: docker.io + registry: ~ # -- Image repository - repository: bitnami/kubectl + repository: reg.nirmata.io/nirmata/kubectl + # -- Image tag # Defaults to `latest` if omitted - tag: '1.30.2' + tag: '1.32.1' # -- (string) Image pull policy # Defaults to image.pullPolicy if omitted pullPolicy: ~ diff --git a/fips.go b/fips.go new file mode 100644 index 00000000..e52db27e --- /dev/null +++ b/fips.go @@ -0,0 +1,20 @@ +//go:build fips +// +build fips + +package main + +/* +This file will only be compiled when BUILD_TAGS=fips. + +Package fipsonly enforces FIPS settings via init() function which + 1. Forces the application to use FIPS-compliant TLS configurations. + 2. Restricts cryptographic operations to those allowed under FIPS standards. + +This package is available when Go is compiled with GOEXPERIMENT=systemcrypto for Go version 1.21 and above. + +Refer Link: https://go.dev/src/crypto/tls/fipsonly/fipsonly.go +*/ + +import ( + _ "crypto/tls/fipsonly" +) //nolint:golint,unused