diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index a7d3e834d..000000000 --- a/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -/.git -/Dockerfile -/Dockerfile.alpine diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f1d5f41a..0b7168b02 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,64 +2,200 @@ name: Release on: push: - tags: - - "v*" + tags: [ 'v*' ] + +permissions: + contents: read jobs: - tagged-release: - name: "Tagged Release" + release: runs-on: ubuntu-latest + permissions: + contents: write # For creating the GitHub release. + id-token: write # For creating OIDC tokens for signing. + packages: write # For pushing and signing container images. + + outputs: + artifact-subjects: "${{ steps.artifact-hashes.outputs.subjects }}" + package-subjects: "${{ steps.package-hashes.outputs.subjects }}" + sbom-subjects: "${{ steps.sbom-hashes.outputs.subjects }}" + container-subjects: "${{ steps.container-metadata.outputs.subjects }}" + steps: - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install git ruby rpm -y - - name: Install fpm - run: gem install fpm || sudo gem install fpm - - name: Set up Go 1.20 - uses: actions/setup-go@v3 + - name: Checkout + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + with: + go-version: 1.20.x + cache: false + + - name: Setup Syft + uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3 + + - name: Setup Cosign + uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1 + + - name: Setup QEMU + uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2.9.1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 with: - go-version: '1.20' - id: go - - name: Check out code into the Go module directory - uses: actions/checkout@v3 - - name: Go vendor - run: go mod vendor - - name: Make release directory - run: mkdir dist - - name: Build deb and rpm - run: make deb-pkg rpm-pkg - - name: Move deb and rpm into release directory - run: mv *.deb *.rpm dist/ - - name: Set RELEASE_VERSION - run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - - name: Set RELEASE_NUMBER - run: echo "RELEASE_NUMBER=$(echo $RELEASE_VERSION | cut -c2-)" >> $GITHUB_ENV - - name: Build linux amd64 binary - run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.linux.amd64 github.com/getsops/sops/v3/cmd/sops && cp dist/sops-${{ env.RELEASE_VERSION }}.linux.amd64 dist/sops-${{ env.RELEASE_VERSION }}.linux - - name: Build linux arm64 binary - run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.linux.arm64 github.com/getsops/sops/v3/cmd/sops - - name: Build darwin amd64 binary - run: GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.darwin.amd64 github.com/getsops/sops/v3/cmd/sops - - name: Copy darwin amd64 to have a no-architecture labeled version - run: cp dist/sops-${{ env.RELEASE_VERSION }}.darwin.amd64 dist/sops-${{ env.RELEASE_VERSION }}.darwin - - name: Build darwin arm64 binary - run: GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.darwin.arm64 github.com/getsops/sops/v3/cmd/sops - - name: Build windows binary - run: GOOS=windows CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.exe github.com/getsops/sops/v3/cmd/sops - - name: Create release - uses: "mozilla/action-automatic-releases@latest" + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to Quay.io + uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - prerelease: true - files: | - dist/sops-${{ env.RELEASE_VERSION }}.exe - dist/sops-${{ env.RELEASE_VERSION }}.darwin.amd64 - dist/sops-${{ env.RELEASE_VERSION }}.darwin.arm64 - dist/sops-${{ env.RELEASE_VERSION }}.darwin - dist/sops-${{ env.RELEASE_VERSION }}.linux.amd64 - dist/sops-${{ env.RELEASE_VERSION }}.linux.arm64 - dist/sops-${{ env.RELEASE_VERSION }}.linux - dist/sops_${{ env.RELEASE_NUMBER }}_amd64.deb - dist/sops_${{ env.RELEASE_NUMBER }}_arm64.deb - dist/sops-${{ env.RELEASE_NUMBER }}-1.x86_64.rpm - dist/sops-${{ env.RELEASE_NUMBER }}-1.aarch64.rpm + registry: quay.io + username: ${{ secrets.QUAY_BOT_USERNAME }} + password: ${{ secrets.QUAY_BOT_TOKEN }} + + - name: Run GoReleaser + id: goreleaser + uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0 + with: + version: 1.20.x + args: release --clean --timeout 1h + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract artifact subjects + id: artifact-hashes + env: + ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" + run: | + set -euo pipefail + sum_file=$(echo -E $ARTIFACTS | jq -r '.[] | {name, "digest": (.extra.Digest // .extra.Checksum)} | select(.digest) | {digest} + {name} | join(" ") | sub("^(.*?):";"")') + echo "subjects=$(echo "$sum_file" | base64 -w0)" >> "$GITHUB_OUTPUT" + + - name: Extract package subjects + id: package-hashes + env: + ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" + run: | + set -euo pipefail + + sum_file="$(mktemp)" + + mapfile -t file_paths < <(echo -E "$ARTIFACTS" | jq -r '.[] | select(.type=="Linux Package") | .path') + for f in "${file_paths[@]}"; do + file_name=$(basename "$f") + file_sum=$(sha256sum "$f" | awk '{print $1}') + echo "$file_sum $file_name" >> "$sum_file" + done + + echo "subjects=$(base64 -w0 < "$sum_file")" >> "$GITHUB_OUTPUT" + + - name: Extract SBOM subjects + id: sbom-hashes + env: + ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" + run: | + set -euo pipefail + + sum_file="$(mktemp)" + + mapfile -t file_paths < <(echo -E "$ARTIFACTS" | jq -r '.[] | select(.type=="SBOM") | .path') + for f in "${file_paths[@]}"; do + file_name=$(basename "$f") + file_sum=$(sha256sum "$f" | awk '{print $1}') + echo "$file_sum $file_name" >> "$sum_file" + done + + echo "subjects=$(base64 -w0 < "$sum_file")" >> "$GITHUB_OUTPUT" + + - name: Extract container image subjects + id: container-metadata + env: + ARTIFACTS: "${{ steps.goreleaser.outputs.artifacts }}" + run: | + image_list=$(echo -e "$ARTIFACTS" | jq -r '.[] | select(.type=="Docker Manifest") | {"image": (.name | sub("^.*?/"; "") | sub(":(.*)"; "")), "digest": .extra.Digest}') + echo "subjects=$(echo $image_list | jq -c -s 'unique_by(.digest) | {"include": .}')" >> "$GITHUB_OUTPUT" + + combine-subjects: + runs-on: ubuntu-latest + + needs: [ release ] + + outputs: + all-subjects: "${{ steps.combine-subjects.outputs.subjects }}" + + steps: + - name: Combine subjects + id: combine-subjects + env: + ARTIFACT_SUBJECTS: "${{ needs.release.outputs.artifact-subjects }}" + PACKAGE_SUBJECTS: "${{ needs.release.outputs.package-subjects }}" + SBOM_SUBJECTS: "${{ needs.release.outputs.sbom-subjects }}" + run: | + set -euo pipefail + + artifact_subjects=$(echo "$ARTIFACT_SUBJECTS" | base64 -d) + package_subjects=$(echo "$PACKAGE_SUBJECTS" | base64 -d) + sbom_subjects=$(echo "$SBOM_SUBJECTS" | base64 -d) + + all_subjects=$(echo -e "${artifact_subjects}\n${package_subjects}\n${sbom_subjects}\n" | sed '/^$/d') + + echo "subjects=$(echo "$all_subjects" | base64 -w0)" >> "$GITHUB_OUTPUT" + + assets-provenance: + needs: [ combine-subjects ] + + permissions: + actions: read # For detecting the GitHub Actions environment. + id-token: write # For creating OIDC tokens for signing. + contents: write # For adding assets to a release. + + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.8.0 + with: + base64-subjects: "${{ needs.combine-subjects.outputs.all-subjects }}" + upload-assets: true + provenance-name: "provenance.intoto.jsonl" + + ghcr-container-provenance: + needs: [ release ] + + permissions: + actions: read # For detecting the Github Actions environment. + id-token: write # For creating OIDC tokens for signing. + packages: write # For uploading attestations. + + strategy: + matrix: ${{ fromJSON(needs.release.outputs.container-subjects) }} + + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.8.0 + with: + image: ghcr.io/${{ matrix.image }} + digest: ${{ matrix.digest }} + registry-username: ${{ github.actor }} + secrets: + registry-password: ${{ secrets.GITHUB_TOKEN }} + + quay-container-provenance: + needs: [ release ] + + permissions: + actions: read # For detecting the Github Actions environment. + id-token: write # For creating OIDC tokens for signing. + packages: write # For uploading attestations. + + strategy: + matrix: ${{ fromJSON(needs.release.outputs.container-subjects) }} + + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.8.0 + with: + image: quay.io/${{ matrix.image }} + digest: ${{ matrix.digest }} + secrets: + registry-username: ${{ secrets.QUAY_BOT_USERNAME }} + registry-password: ${{ secrets.QUAY_BOT_TOKEN }} diff --git a/.gitignore b/.gitignore index d7e97440f..721fabef4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -target +bin/ +dist/ Cargo.lock vendor/ coverage.txt diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 000000000..db87d598b --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,370 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json + +project_name: sops + +# xref: https://goreleaser.com/customization/hooks/ +before: + hooks: + - go mod download + +# xref: https://goreleaser.com/customization/env/ +env: + - CGO_ENABLED=0 + - PKG=github.com/getsops/sops/v3/version + - COSIGN_YES=true + +# xref: https://goreleaser.com/customization/reportsizes/ +report_sizes: true + +# xref: https://goreleaser.com/customization/build/ +builds: + - id: binary-linux + main: ./cmd/sops + binary: "{{ .ProjectName }}" + flags: + - -trimpath + - -mod=readonly + ldflags: + - > + -s -w + -X {{ .Env.PKG }}.Version={{ .Version }}" + goos: + - linux + goarch: + - amd64 + - arm64 + # Modified timestamp on the binary, set to ensure reproducible builds. + mod_timestamp: "{{ .CommitTimestamp }}" + + - id: binary-darwin + main: ./cmd/sops + binary: "{{ .ProjectName }}" + flags: + - -trimpath + - -mod=readonly + ldflags: + - > + -s -w + -X {{ .Env.PKG }}.Version={{ .Version }} + goos: + - darwin + goarch: + - amd64 + - arm64 + # Modified timestamp on the binary, set to ensure reproducible builds. + mod_timestamp: "{{ .CommitTimestamp }}" + + - id: binary-windows + main: ./cmd/sops + binary: "{{ .ProjectName }}" + flags: + - -trimpath + - -buildmode=pie + - -mod=readonly + ldflags: + - > + -s -w + -X {{ .Env.PKG }}.Version={{ .Version }} + goos: + - windows + goarch: + - amd64 + # Modified timestamp on the binary, set to ensure reproducible builds. + mod_timestamp: "{{ .CommitTimestamp }}" + +# xref: https://goreleaser.com/customization/universalbinaries/ +universal_binaries: + - id: binary-darwin-universal + ids: + - binary-darwin + name_template: '{{ .ProjectName }}' + # We want to continue to ship individual binaries for darwin/amd64 and + # darwin/arm64. + replace: false + # Modified timestamp on the binary, set to ensure reproducible builds. + # NB: Available in GoReleaser >=1.20.0. + mod_timestamp: "{{ .CommitTimestamp }}" + +# xref: https://goreleaser.com/customization/nfpm/ +nfpms: + - id: deb + package_name: '{{ .ProjectName }}' + file_name_template: '{{ .ConventionalFileName }}' + vendor: CNCF SOPS + homepage: https://github.com/{{ .Env.GITHUB_REPOSITORY }} + maintainer: SOPS maintainers + description: Simple and flexible tool for managing secrets + license: MPL-2.0 + formats: + - deb + - rpm + +# xref: https://goreleaser.com/customization/snapshots/ +snapshot: + name_template: "{{ incpatch .Version }}-dev-{{ .ShortCommit }}" + +# xref: https://goreleaser.com/customization/archive/#disable-archiving +archives: + - id: archive-unix + format: binary + builds: + - binary-linux + - binary-darwin + # NB: specifically crafted to ensure compatibility with release artifacts < v3.8.0. + name_template: '{{ .ProjectName }}-v{{ .Version }}.{{ .Os }}.{{ .Arch }}' + + - id: archive-windows + format: binary + builds: + - binary-windows + # NB: specifically crafted to ensure compatibility with release artifacts < v3.8.0. + name_template: '{{ .ProjectName }}-v{{ .Version }}' + + - id: archive-darwin-universal + format: binary + builds: + - binary-darwin-universal + # NB: specifically crafted to ensure compatibility with release artifacts < v3.8.0. + # We can't bundle this with the other unix archive, because .Arch becomes "all". + # Before v3.8.0, this used to be _just_ the AMD64 binary. + name_template: '{{ .ProjectName }}-v{{ .Version }}.darwin' + +# xref: https://goreleaser.com/customization/checksum/ +checksum: + name_template: "{{ .ProjectName }}-v{{ .Version }}.checksums.txt" + algorithm: sha256 + ids: + - archive-unix + - archive-windows + - archive-darwin-universal + +# xref: https://goreleaser.com/customization/sbom/ +sboms: + - id: binary-sbom + artifacts: binary + documents: + - "{{ .ArtifactName }}.spdx.sbom.json" + +# xref: https://goreleaser.com/customization/sign/ +signs: + - cmd: cosign + artifacts: checksum + signature: '{{ trimsuffix .Env.artifact ".txt" }}.sig' + certificate: '{{ trimsuffix .Env.artifact ".txt" }}.pem' + args: + - "sign-blob" + - "--output-signature" + - "${signature}" + - "--output-certificate" + - "${certificate}" + - "${artifact}" + output: true + +# xref: https://goreleaser.com/customization/docker/ +dockers: + - image_templates: + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-amd64' + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-amd64' + use: buildx + goos: linux + goarch: amd64 + ids: + - binary-linux + dockerfile: .release/Dockerfile + build_flag_templates: + - "--pull" + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .GitURL }}" + + - image_templates: + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-arm64' + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-arm64' + use: buildx + goos: linux + goarch: arm64 + ids: + - binary-linux + dockerfile: .release/Dockerfile + build_flag_templates: + - "--pull" + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .GitURL }}" + + - image_templates: + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-amd64' + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-amd64' + use: buildx + goos: linux + goarch: amd64 + ids: + - binary-linux + dockerfile: .release/alpine.Dockerfile + build_flag_templates: + - "--pull" + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .GitURL }}" + + - image_templates: + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-arm64' + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-arm64' + use: buildx + goos: linux + goarch: arm64 + ids: + - binary-linux + dockerfile: .release/alpine.Dockerfile + build_flag_templates: + - "--pull" + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{ .Date }}" + - "--label=org.opencontainers.image.title={{ .ProjectName }}" + - "--label=org.opencontainers.image.revision={{ .FullCommit }}" + - "--label=org.opencontainers.image.version={{ .Version }}" + - "--label=org.opencontainers.image.source={{ .GitURL }}" + +# xref: https://goreleaser.com/customization/docker_manifest/ +docker_manifests: + - name_template: 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}' + image_templates: + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-amd64' + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-arm64' + + - name_template: 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine' + image_templates: + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-amd64' + - 'ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-arm64' + + - name_template: 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}' + image_templates: + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-amd64' + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-arm64' + + - name_template: 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine' + image_templates: + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-amd64' + - 'quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine-arm64' + +# xref: https://goreleaser.com/customization/docker_sign/ +docker_signs: + - cmd: cosign + artifacts: all + output: true + args: + - "sign" + - "${artifact}@${digest}" + +# xref: https://goreleaser.com/customization/changelog/ +changelog: + # xref: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuration-options + # xref: https://docs.github.com/en/free-pro-team@latest/rest/releases/releases?apiVersion=2022-11-28#generate-release-notes-content-for-a-release + use: github-native + +# xref: https://goreleaser.com/customization/release/ +release: + prerelease: auto + header: | + ## Installation + + To install `{{ .ProjectName }}`, download one of the pre-built binaries provided for your platform from the artifacts attached to this release. + + For instance, if you are using Linux on an AMD64 architecture: + + ```shell + # Download the binary + curl -LO https://github.com/{{ .Env.GITHUB_REPOSITORY }}/releases/download/{{ .Tag }}/{{ .ProjectName }}-v{{ .Version }}.linux.amd64 + + # Move the binary in to your PATH + mv {{ .ProjectName }}-v{{ .Version }}.linux.amd64 /usr/local/bin/{{ .ProjectName }} + + # Make the binary executable + chmod +x /usr/local/bin/{{ .ProjectName }} + ``` + + ### Verify checksums file signature + + The checksums file provided within the artifacts attached to this release is signed using [Cosign](https://docs.sigstore.dev/cosign/overview/) with GitHub OIDC. To validate the signature of this file, run the following commands: + + ```shell + # Download the checksums file, certificate and signature + curl -LO https://github.com/{{ .Env.GITHUB_REPOSITORY }}/releases/download/{{ .Tag }}/{{ .ProjectName }}-v{{ .Version }}.checksums.txt + curl -LO https://github.com/{{ .Env.GITHUB_REPOSITORY }}/releases/download/{{ .Tag }}/{{ .ProjectName }}-v{{ .Version }}.checksums.pem + curl -LO https://github.com/{{ .Env.GITHUB_REPOSITORY }}/releases/download/{{ .Tag }}/{{ .ProjectName }}-v{{ .Version }}.checksums.sig + + # Verify the checksums file + cosign verify-blob {{ .ProjectName }}-v{{ .Version }}.checksums.txt \ + --certificate {{ .ProjectName }}-v{{ .Version }}.checksums.pem \ + --signature {{ .ProjectName }}-v{{ .Version }}.checksums.sig \ + --certificate-identity-regexp=https://github.com/{{ .Env.GITHUB_REPOSITORY_OWNER }} \ + --certificate-oidc-issuer=https://token.actions.githubusercontent.com + ``` + + ### Verify binary integrity + + To verify the integrity of the downloaded binary, you can utilize the checksums file after having validated its signature: + + ```shell + # Verify the binary using the checksums file + sha256sum -c {{ .ProjectName }}-v{{ .Version }}.checksums.txt --ignore-missing + ``` + + ### Verify artifact provenance + + The [SLSA provenance](https://slsa.dev/provenance/v0.2) of the binaries, packages, and SBOMs can be found within the artifacts associated with this release. It is presented through an [in-toto](https://in-toto.io/) link metadata file named `provenance.intoto.jsonl`. To verify the provenance of an artifact, you can utilize the [`slsa-verifier`](https://github.com/slsa-framework/slsa-verifier#artifacts) tool: + + ```shell + # Download the metadata file + curl -LO https://github.com/{{ .Env.GITHUB_REPOSITORY }}/releases/download/{{ .Tag }}/provenance.intoto.jsonl + + # Verify the provenance of the artifact + slsa-verifier verify-artifact \ + --provenance-path provenance.intoto.jsonl \ + --source-uri github.com/{{ .Env.GITHUB_REPOSITORY }} \ + --source-tag {{ .Tag }} + ``` + + ## Container Images + + The `{{ .ProjectName }}` binaries are also available as container images, based on Debian (slim) and Alpine Linux. The Debian-based container images include any dependencies which may be required to make use of certain key services, such as GnuPG, AWS KMS, Azure Key Vault, and Google Cloud KMS. The Alpine-based container images are smaller in size, but do not include these dependencies. + + These container images are available for the following architectures: `linux/amd64` and `linux/arm64`. + + ### GitHub Container Registry + + - `ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}` + - `ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine` + + ### Quay.io + + - `quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}` + - `quay.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }}-alpine` + + ### Verify container image signature + + The container images are signed using [Cosign](https://docs.sigstore.dev/cosign/overview/) with GitHub OIDC. To validate the signature of an image, run the following command: + + ```shell + cosign verify ghcr.io/{{ .Env.GITHUB_REPOSITORY }}:v{{ .Version }} \ + --certificate-identity-regexp=https://github.com/{{ .Env.GITHUB_REPOSITORY_OWNER }} \ + --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ + -o text + ``` + + ### Verify container image provenance + + The container images include [SLSA provenance](https://slsa.dev/provenance/v0.2) attestations. For more information around the verification of this, please refer to the [`slsa-verifier` documentation](https://github.com/slsa-framework/slsa-verifier#containers). + + ## Software Bill of Materials + + The Software Bill of Materials (SBOM) for each binary is accessible within the artifacts enclosed with this release. It is presented as an [SPDX](https://spdx.dev/) JSON file, formatted as `.spdx.sbom.json`. diff --git a/.release/Dockerfile b/.release/Dockerfile new file mode 100644 index 000000000..b85ef1005 --- /dev/null +++ b/.release/Dockerfile @@ -0,0 +1,16 @@ +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install --no-install-recommends -y \ + awscli \ + azure-cli \ + curl \ + gnupg \ + vim \ + && rm -rf /var/lib/apt/lists/* + +ENV EDITOR vim + +# Glob pattern to match the binary for the current architecture +COPY sops* /usr/local/bin/sops + +ENTRYPOINT ["sops"] diff --git a/.release/alpine.Dockerfile b/.release/alpine.Dockerfile new file mode 100644 index 000000000..eaffdfd73 --- /dev/null +++ b/.release/alpine.Dockerfile @@ -0,0 +1,13 @@ +FROM alpine:3.18 + +RUN apk --no-cache add \ + ca-certificates \ + vim \ + && update-ca-certificates + +ENV EDITOR vim + +# Glob pattern to match the binary for the current architecture +COPY sops* /usr/local/bin/sops + +ENTRYPOINT ["sops"] diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 6db37f1fa..000000000 --- a/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM golang:1.20 - -COPY . /go/src/github.com/getsops/sops/v3 -WORKDIR /go/src/github.com/getsops/sops/v3 - -RUN CGO_ENABLED=1 make install -RUN apt-get update -RUN apt-get install -y vim python3-pip emacs -RUN pip install awscli -ENV EDITOR vim diff --git a/Dockerfile.alpine b/Dockerfile.alpine deleted file mode 100644 index 583a83ce0..000000000 --- a/Dockerfile.alpine +++ /dev/null @@ -1,17 +0,0 @@ -FROM golang:1.20-alpine3.18 AS builder - -RUN apk --no-cache add make - -COPY . /go/src/github.com/getsops/sops/v3 -WORKDIR /go/src/github.com/getsops/sops/v3 - -RUN CGO_ENABLED=1 make install - - -FROM alpine:3.18 - -RUN apk --no-cache add \ - vim ca-certificates -ENV EDITOR vim -COPY --from=builder /go/bin/sops /usr/local/bin/sops -ENTRYPOINT ["/usr/local/bin/sops"] diff --git a/Makefile b/Makefile index 32f06f737..334bdcff8 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,20 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -PROJECT := github.com/getsops/sops/v3 -GO := GOPROXY=https://proxy.golang.org go -GOLINT := golint +PROJECT := github.com/getsops/sops/v3 +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +GO := GOPROXY=https://proxy.golang.org go +GOLINT := golint +GITHUB_REPOSITORY ?= github.com/getsops/sops + +GORELEASER := $(PROJECT_DIR)/bin/goreleaser +GORELEASER_VERSION ?= v1.20.0 + +.PHONY: all all: test vet generate install functional-tests + +.PHONY: origin-build origin-build: test vet generate install functional-tests-all install: @@ -18,6 +27,7 @@ tag: all lint: $(GOLINT) $(PROJECT) +.PHONY: vendor vendor: $(GO) mod tidy $(GO) mod vendor @@ -25,6 +35,7 @@ vendor: vet: $(GO) vet $(PROJECT) +.PHONY: test test: vendor gpg --import pgp/sops_functional_tests_key.asc 2>&1 1>/dev/null || exit 0 ./test.sh @@ -32,81 +43,38 @@ test: vendor showcoverage: test $(GO) tool cover -html=coverage.out +.PHONY: generate generate: keyservice/keyservice.pb.go $(GO) generate %.pb.go: %.proto protoc --go_out=plugins=grpc:. $< +.PHONY: functional-tests functional-tests: $(GO) build -o functional-tests/sops github.com/getsops/sops/v3/cmd/sops cd functional-tests && cargo test -# Ignored tests are ones that require external services (e.g. AWS KMS) -# TODO: Once `--include-ignored` lands in rust stable, switch to that. +.PHONY: functional-tests-all functional-tests-all: $(GO) build -o functional-tests/sops github.com/getsops/sops/v3/cmd/sops + # Ignored tests are ones that require external services (e.g. AWS KMS) + # TODO: Once `--include-ignored` lands in rust stable, switch to that. cd functional-tests && cargo test && cargo test -- --ignored -# Creates variables during target re-definition. Basically this block allows the particular variables to be used in the final target -build-deb-%: OS = $(word 1,$(subst -, ,$*)) -build-deb-%: ARCH = $(word 2,$(subst -, ,$*)) -build-deb-%: FPM_ARCH = $(word 3,$(subst -, ,$*)) -# Poor-mans function with parameters being split out from the variable part of it's name -build-deb-%: - rm -rf tmppkg - mkdir -p tmppkg/usr/local/bin - GOOS=$(OS) GOARCH="$(ARCH)" CGO_ENABLED=0 go build -mod vendor -o tmppkg/usr/local/bin/sops github.com/getsops/sops/v3/cmd/sops - fpm -C tmppkg -n sops --license MPL2.0 --vendor mozilla \ - --description "Sops is an editor of encrypted files that supports YAML, JSON and BINARY formats and encrypts with AWS KMS and PGP." \ - -m "AJ Bahnken " \ - --url https://github.com/getsops/sops/v3 \ - --architecture $(FPM_ARCH) \ - -v "$$(grep '^const Version' version/version.go |cut -d \" -f 2)" \ - -s dir -t deb . - -# Create .deb packages for multiple architectures -deb-pkg: vendor build-deb-linux-amd64-x86_64 build-deb-linux-arm64-arm64 - -# Creates variables during target re-definition. Basically this block allows the particular variables to be used in the final target -build-rpm-%: OS = $(word 1,$(subst -, ,$*)) -build-rpm-%: ARCH = $(word 2,$(subst -, ,$*)) -build-rpm-%: FPM_ARCH = $(word 3,$(subst -, ,$*)) -# Poor-mans function with parameters being split out from the variable part of it's name -build-rpm-%: - rm -rf tmppkg - mkdir -p tmppkg/usr/local/bin - GOOS=$(OS) GOARCH="$(ARCH)" CGO_ENABLED=0 go build -mod vendor -o tmppkg/usr/local/bin/sops github.com/getsops/sops/v3/cmd/sops - fpm -C tmppkg -n sops --license MPL2.0 --vendor mozilla \ - --description "Sops is an editor of encrypted files that supports YAML, JSON and BINARY formats and encrypts with AWS KMS and PGP." \ - -m "AJ Bahnken " \ - --url https://github.com/getsops/sops/v3 \ - --architecture $(FPM_ARCH) \ - --rpm-os $(OS) \ - -v "$$(grep '^const Version' version/version.go |cut -d \" -f 2)" \ - -s dir -t rpm . - -# Create .rpm packages for multiple architectures -rpm-pkg: vendor build-rpm-linux-amd64-x86_64 build-rpm-linux-arm64-arm64 - -dmg-pkg: install -ifneq ($(OS),darwin) - echo 'you must be on MacOS and set OS=darwin on the make command line to build an OSX package' -else - rm -rf tmppkg - mkdir -p tmppkg/usr/local/bin - cp $$GOPATH/bin/sops tmppkg/usr/local/bin/ - fpm -C tmppkg -n sops --license MPL2.0 --vendor mozilla \ - --description "Sops is an editor of encrypted files that supports YAML, JSON and BINARY formats and encrypts with AWS KMS and PGP." \ - -m "Mozilla Security " \ - --url https://github.com/getsops/sops/v3 \ - --architecture x86_64 \ - -v "$$(grep '^const Version' version/version.go |cut -d \" -f 2)" \ - -s dir -t osxpkg \ - --osxpkg-identifier-prefix org.mozilla.sops \ - -p tmppkg/sops-$$(git describe --abbrev=0 --tags).pkg . - hdiutil makehybrid -hfs -hfs-volume-name "Mozilla Sops" \ - -o tmppkg/sops-$$(git describe --abbrev=0 --tags).dmg tmpdmg -endif - -.PHONY: all test generate clean vendor functional-tests +.PHONY: release-snapshot +release-snapshot: install-goreleaser + GITHUB_REPOSITORY=$(GITHUB_REPOSITORY) $(GORELEASER) release --clean --snapshot --skip-sign + +.PHONY: install-goreleaser +install-goreleaser: + $(call go-install-tool,$(GORELEASER),github.com/goreleaser/goreleaser@$(GORELEASER_VERSION),$(GORELEASER_VERSION)) + +# go-install-tool will 'go install' any package $2 and install it to $1. +define go-install-tool +@[ -f $(1)-$(3) ] || { \ +set -e ;\ +GOBIN=$$(dirname $(1)) go install $(2) ;\ +touch $(1)-$(3) ;\ +} +endef diff --git a/bin/ci/deploy_dockerhub.sh b/bin/ci/deploy_dockerhub.sh deleted file mode 100755 index 89a7dc310..000000000 --- a/bin/ci/deploy_dockerhub.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# THIS IS MEANT TO BE RUN BY CI - -set -e -set +x - -# Usage: retry MAX CMD... -# Retry CMD up to MAX times. If it fails MAX times, returns failure. -# Example: retry 3 docker push "mozilla/sops:$TAG" -function retry() { - max=$1 - shift - count=1 - until "$@"; do - count=$((count + 1)) - if [[ $count -gt $max ]]; then - return 1 - fi - echo "$count / $max" - done - return 0 -} -if [[ "$DOCKER_DEPLOY" == "true" ]]; then - # configure docker creds - retry 3 docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" - # docker tag and push git branch to dockerhub - if [ -n "$1" ]; then - retry 3 docker push "mozilla/sops:$1" || - (echo "Couldn't push mozilla/sops:$1" && false) - echo "Pushed mozilla/sops:$1" - fi -fi diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 000000000..42207b35c --- /dev/null +++ b/docs/release.md @@ -0,0 +1,74 @@ +# Release procedure + +This document describes the procedure for releasing a new version of SOPS. It +is intended for maintainers of the project, but may be useful for anyone +interested in the release process. + +## Overview + +The release is performed by creating a signed tag for the release, and pushing +it to GitHub. This will automatically trigger a GitHub Actions workflow that +builds the binaries, packages, SBOMs, and other artifacts for the release +using [GoReleaser](https://goreleaser.com), and uploads them to GitHub. + +The configuration for GoReleaser is in the file +[`.goreleaser.yaml`](../.goreleaser.yaml). The configuration for the GitHub +Actions workflow is in the file +[`release.yml`](../.github/workflows/release.yml). + +This configuration is quite sophisticated, and ensures at least the following: + +- The release is built for multiple platforms and architectures, including + Linux, macOS, and Windows, and for both AMD64 and ARM64. +- The release includes multiple packages in Debian and RPM formats. +- For every binary, a corresponding SBOM is generated and published. +- For all binaries, a checksum file is generated and signed using + [Cosign](https://docs.sigstore.dev/cosign/overview/) with GitHub OIDC. +- Both Debian and Alpine Docker multi-arch images are built and pushed to GitHub + Container Registry and Quay.io. +- The container images are signed using + [Cosign](https://docs.sigstore.dev/cosign/overview/) with GitHub OIDC. +- [SLSA provenance](https://slsa.dev/provenance/v0.2) metadata is generated for + release artifacts and container images. + +## Preparation + +- [ ] Ensure that all changes intended for the release are merged into the + `main` branch. At present, this means that all pull requests attached to the + milestone for the release are merged. If there are any pull requests that + should not be included in the release, move them to a different milestone. +- [ ] Create a pull request to update the [`CHANGELOG.rst`](../CHANGELOG.rst) + file. This should include a summary of all changes since the last release, + including references to any relevant pull requests. +- [ ] In this same pull request, update the version number in `version/version.go` + to the new version number. +- [ ] Get approval for the pull request from at least one other maintainer, and + merge it into `main`. +- [ ] Ensure CI passes on the `main` branch. + +## Release + +- [ ] Ensure your local copy of the `main` branch is up-to-date: + + ```sh + git checkout main + git pull + ``` +- [ ] Create a **signed tag** for the release, using the following command: + + ```sh + git tag -s -m + ``` + + where `` is the version number of the release. The version number + should be in the form `vX.Y.Z`, where `X`, `Y`, and `Z` are integers. The + version number should be incremented according to + [semantic versioning](https://semver.org/). +- [ ] Push the tag to GitHub: + + ```sh + git push origin + ``` + +- [ ] Ensure the release is built successfully on GitHub Actions. This will + automatically create a release on GitHub. diff --git a/version/version.go b/version/version.go index db73522a9..0527b5255 100644 --- a/version/version.go +++ b/version/version.go @@ -11,7 +11,7 @@ import ( ) // Version represents the value of the current semantic version -const Version = "3.7.3" +var Version = "3.7.3" // PrintVersion handles the version command for sops func PrintVersion(c *cli.Context) {