diff --git a/.github/scripts/mac-prepare-for-signing.sh b/.github/scripts/mac-prepare-for-signing.sh new file mode 100755 index 00000000..7d0045d4 --- /dev/null +++ b/.github/scripts/mac-prepare-for-signing.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -eu + +CI_HOME="/Users/runner" +if [[ "${HOME}" != "${CI_HOME}" ]]; then + printf "WARNING! It looks like this isn't the CI environment. This script modifies the macOS Keychain setup in ways you probably wouldn't want for your own machine. It also requires an Apple Developer ID Certificate that you shouldn't have outside of the CI environment.\n\nExiting early to make sure nothing bad happens.\n" + exit 1 +fi + +# Install gon (see https://github.com/mitchellh/gon for details). +brew tap mitchellh/gon +brew install mitchellh/gon/gon + +# Write signing certificate to disk from environment variable. +CERT_FILE="$HOME/developer_id_certificate.p12" +echo -n "$APPLE_DEVELOPER_ID_CERT" | base64 --decode > "$CERT_FILE" + +# In order to have all keychain interactions avoid an interactive user prompt, we need to control the password for the keychain in question, which means we need to create a new keychain into which we'll import the signing certificate and from which we'll later access this certificate during code signing. +EPHEMERAL_KEYCHAIN="ci-ephemeral-keychain" +EPHEMERAL_KEYCHAIN_PASSWORD="$(openssl rand -base64 100)" +security create-keychain -p "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN}" + +# Import signing certificate into the keychain. (This is a pre-requisite for gon, which is invoked via goreleaser.) +EPHEMERAL_KEYCHAIN_FULL_PATH="$HOME/Library/Keychains/${EPHEMERAL_KEYCHAIN}-db" +security import "${CERT_FILE}" -k "${EPHEMERAL_KEYCHAIN_FULL_PATH}" -P "${APPLE_DEVELOPER_ID_CERT_PASS}" -T "$(command -v codesign)" + +# Setting the partition list for this certificate's private key to include "apple-tool:" and "apple:" allows the codesign command to access this keychain item without an interactive user prompt. (codesign is invoked by gon.) +security set-key-partition-list -S "apple-tool:,apple:" -s -k "${EPHEMERAL_KEYCHAIN_PASSWORD}" "${EPHEMERAL_KEYCHAIN_FULL_PATH}" + +# Make this new keychain the user's default keychain, so that codesign will be able to find this certificate when we specify it during signing. +security default-keychain -d "user" -s "${EPHEMERAL_KEYCHAIN_FULL_PATH}" diff --git a/.github/scripts/mac-sign-and-notarize.sh b/.github/scripts/mac-sign-and-notarize.sh new file mode 100755 index 00000000..9d89ad83 --- /dev/null +++ b/.github/scripts/mac-sign-and-notarize.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eu + +IS_SNAPSHOT="$1" # e.g. "true", "false" + +if [[ "${IS_SNAPSHOT}" == "true" ]]; then + # This is a snapshot build —— skipping signing and notarization... + exit 0 +fi + +GON_CONFIG="$2" # e.g. "gon.hcl" +NEW_DMG_NAME="$3" # e.g. "./dist/syft-0.1.0.dmg" +ORIGINAL_DMG_NAME="./dist/output.dmg" # This should match dmg output_path in the gon config file. + +gon "${GON_CONFIG}" +mv -v "${ORIGINAL_DMG_NAME}" "${NEW_DMG_NAME}" diff --git a/.github/scripts/update-version-file.sh b/.github/scripts/update-version-file.sh index 5b8f5d78..9c3b56d0 100755 --- a/.github/scripts/update-version-file.sh +++ b/.github/scripts/update-version-file.sh @@ -15,14 +15,9 @@ fi echo "creating and publishing version file" # create a version file for version-update checks -echo "${VERSION}" | tee ${DISTDIR}/VERSION +VERSION_FILE="${DISTDIR}/VERSION" +echo "${VERSION}" | tee "${VERSION_FILE}" # upload the version file that supports the application version update check -docker run --rm \ - -i \ - -e AWS_DEFAULT_REGION=us-west-2 \ - -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ - -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ - -v $(pwd)/${DISTDIR}/:/distmount \ - amazon/aws-cli \ - s3 cp /distmount/VERSION s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION \ No newline at end of file +export AWS_DEFAULT_REGION=us-west-2 +aws s3 cp "${VERSION_FILE}" s3://toolbox-data.anchore.io/${BIN}/releases/latest/VERSION diff --git a/.github/workflows/acceptance-test.yaml b/.github/workflows/acceptance-test.yaml index 0d02a4e2..e058d880 100644 --- a/.github/workflows/acceptance-test.yaml +++ b/.github/workflows/acceptance-test.yaml @@ -15,7 +15,7 @@ env: jobs: # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline Build-Snapshot-Artifacts: - runs-on: ubuntu-latest + runs-on: macos-latest # We're creating these snapshot builds on macOS to be consistent with our release workflow's build process, which also takes place on macOS (due to code signing requirements). steps: - uses: actions/setup-go@v2 @@ -36,10 +36,6 @@ jobs: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}- ${{ runner.os }}-go-${{ env.GO_VERSION }}- - - name: Bootstrap dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: make ci-bootstrap - - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v2 @@ -92,4 +88,4 @@ jobs: path: snapshot - name: Compare Anchore inline-scan results against snapshot build output - run: make compare-snapshot \ No newline at end of file + run: make compare-snapshot diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e3e1a627..3dffce32 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,7 @@ env: jobs: wait-for-checks: - runs-on: ubuntu-latest + runs-on: ubuntu-latest # This OS choice is arbitrary. None of the steps in this job are specific to either Linux or macOS. steps: - uses: actions/checkout@v2 @@ -71,7 +71,7 @@ jobs: release: needs: [ wait-for-checks ] - runs-on: ubuntu-latest + runs-on: macos-latest # Due to our code signing process, it's vital that we run our release steps on macOS. steps: - uses: actions/setup-go@v2 @@ -82,6 +82,7 @@ jobs: with: fetch-depth: 0 + # We are expecting this cache to have been created during the "Build-Snapshot-Artifacts" job in the "Acceptance" workflow. - name: Restore bootstrap cache id: cache uses: actions/cache@v2 @@ -94,10 +95,6 @@ jobs: ${{ runner.os }}-go-${{ env.GO_VERSION }}-${{ hashFiles('Makefile') }}- ${{ runner.os }}-go-${{ env.GO_VERSION }}- - - name: Bootstrap dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: make ci-bootstrap - - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v2 @@ -121,6 +118,10 @@ jobs: SIGNING_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }} + APPLE_DEVELOPER_ID_CERT: ${{ secrets.APPLE_DEVELOPER_ID_CERT }} # Used during macOS code signing. + APPLE_DEVELOPER_ID_CERT_PASS: ${{ secrets.APPLE_DEVELOPER_ID_CERT_PASS }} # Used during macOS code signing. + AC_USERNAME: ${{ secrets.ENG_CI_APPLE_ID }} # Used during macOS notarization. + AC_PASSWORD: ${{ secrets.ENG_CI_APPLE_ID_PASS }} # Used during macOS notarization. - uses: 8398a7/action-slack@v3 with: @@ -134,4 +135,4 @@ jobs: - uses: actions/upload-artifact@v2 with: name: artifacts - path: dist/**/* \ No newline at end of file + path: dist/**/* diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ae0164a7..4a059179 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -7,12 +7,13 @@ release: # If set to true, will not auto-publish the release. This is done to allow us to review the changelog before publishing. draft: true -signs: - - artifacts: checksum - args: ["--output", "${signature}", "--detach-sign", "${artifact}"] + # This ensures any macOS signed artifacts get included with the release. + extra_files: + - glob: "./dist/*.dmg" builds: - binary: grype + id: grype env: - CGO_ENABLED=0 goos: @@ -32,6 +33,31 @@ builds: -X github.com/anchore/grype/internal/version.gitCommit={{.Commit}} -X github.com/anchore/grype/internal/version.buildDate={{.Date}} -X github.com/anchore/grype/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}} + # For more info on this macOS build, see: https://github.com/mitchellh/gon#usage-with-goreleaser + - binary: grype + id: grype-macos + env: + - CGO_ENABLED=0 + goos: + - darwin + goarch: + - amd64 + # Set the modified timestamp on the output binary to the git timestamp (to ensure a reproducible build) + mod_timestamp: '{{ .CommitTimestamp }}' + ldflags: | + -w + -s + -extldflags '-static' + -X github.com/anchore/grype/internal/version.version={{.Version}} + -X github.com/anchore/grype/internal/version.gitCommit={{.Commit}} + -X github.com/anchore/grype/internal/version.buildDate={{.Date}} + -X github.com/anchore/grype/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}} + hooks: + post: ./.github/scripts/mac-sign-and-notarize.sh "{{.IsSnapshot}}" "gon.hcl" "./dist/grype_{{.Tag}}_{{.Target}}.dmg" + +signs: + - artifacts: checksum + args: ["--output", "${signature}", "--detach-sign", "${artifact}"] nfpms: - license: "Apache 2.0" @@ -51,7 +77,8 @@ brews: archives: - format: tar.gz + builds: + - grype # i.e. Linux only format_overrides: - goos: windows format: zip - diff --git a/Makefile b/Makefile index 39ea54f8..610cbb1b 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,10 @@ help: ci-bootstrap: bootstrap DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y bc jq libxml2-utils +.PHONY: +ci-bootstrap-mac: + github_changelog_generator --version || sudo gem install github_changelog_generator + .PHONY: boostrap bootstrap: ## Download and install all go dependencies (+ prep tooling in the ./tmp dir) $(call title,Boostrapping dependencies) @@ -172,8 +176,7 @@ compare: changelog-release: @echo "Last tag: $(SECOND_TO_LAST_TAG)" @echo "Current tag: $(VERSION)" - @docker run -i --rm \ - -v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \ + @github_changelog_generator \ --user anchore \ --project $(BIN) \ -t ${GITHUB_TOKEN} \ @@ -187,8 +190,9 @@ changelog-release: .PHONY: changelog-unreleased changelog-unreleased: ## show the current changelog that will be produced on the next release (note: requires GITHUB_TOKEN set) - @docker run -it --rm \ - -v "$(shell pwd)":/usr/local/src/your-app ferrarimarco/github-changelog-generator \ + @docker run -it --rm \ + -v "$(shell pwd)":/usr/local/src/your-app \ + ferrarimarco/github-changelog-generator \ --user anchore \ --project $(BIN) \ -t ${GITHUB_TOKEN} \ @@ -204,8 +208,12 @@ changelog-unreleased: ## show the current changelog that will be produced on the /CHANGELOG.md .PHONY: release -release: clean-dist changelog-release ## Build and publish final binaries and packages +release: clean-dist ci-bootstrap-mac changelog-release ## Build and publish final binaries and packages. Intended to be run only on macOS. $(call title,Publishing release artifacts) + + # Prepare for macOS-specific signing process + .github/scripts/mac-prepare-for-signing.sh + # create a config with the dist dir overridden echo "dist: $(DISTDIR)" > $(TEMPDIR)/goreleaser.yaml cat .goreleaser.yaml >> $(TEMPDIR)/goreleaser.yaml diff --git a/gon.hcl b/gon.hcl new file mode 100644 index 00000000..f0d9dff9 --- /dev/null +++ b/gon.hcl @@ -0,0 +1,11 @@ +source = ["./dist/grype-macos_darwin_amd64/grype"] # The 'dist' directory path should ideally reference an env var, where the source of truth is the Makefile. I wasn't able to figure out how to solve this. +bundle_id = "com.anchore.toolbox.grype" + +sign { + application_identity = "Developer ID Application: ANCHORE, INC. (9MJHKYX5AT)" +} + +dmg { + output_path = "./dist/output.dmg" + volume_name = "Grype" +}