diff --git a/.github/scripts/mac-prepare-for-signing.sh b/.github/scripts/mac-prepare-for-signing.sh new file mode 100755 index 000000000..7d0045d4d --- /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 000000000..9d89ad831 --- /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 6155338ff..9d416df79 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 f53d6a237..11afd3ef5 100644 --- a/.github/workflows/acceptance-test.yaml +++ b/.github/workflows/acceptance-test.yaml @@ -14,8 +14,7 @@ env: jobs: Build-Snapshot-Artifacts: - # come Nov 30 2020 ubuntu-latest will be ubuntu-20.04, until then it needs to be explicitly referenced due to python 3.7 specific features being used - runs-on: ubuntu-20.04 + 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 @@ -40,9 +39,6 @@ jobs: if: steps.bootstrap-cache.outputs.cache-hit != 'true' run: make bootstrap - - name: Bootstrap CI dependencies - run: make ci-bootstrap - - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v2 @@ -162,4 +158,4 @@ jobs: text: The syft acceptance tests have failed tragically! env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_TOOLBOX_WEBHOOK_URL }} - if: ${{ failure() }} \ No newline at end of file + if: ${{ failure() }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6388bd8c8..e31af946e 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 @@ -82,7 +82,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 @@ -93,6 +93,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 @@ -105,10 +106,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 @@ -132,6 +129,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: @@ -145,4 +146,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 1368ca89c..0235015d3 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,18 +6,37 @@ 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: syft + id: syft env: - CGO_ENABLED=0 goos: # windows not supported yet (due to jotframe) # - windows - linux + 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/syft/internal/version.version={{.Version}} + -X github.com/anchore/syft/internal/version.gitCommit={{.Commit}} + -X github.com/anchore/syft/internal/version.buildDate={{.Date}} + -X github.com/anchore/syft/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: syft + id: syft-macos + env: + - CGO_ENABLED=0 + goos: - darwin goarch: - amd64 @@ -31,6 +50,12 @@ builds: -X github.com/anchore/syft/internal/version.gitCommit={{.Commit}} -X github.com/anchore/syft/internal/version.buildDate={{.Date}} -X github.com/anchore/syft/internal/version.gitTreeState={{.Env.BUILD_GIT_TREE_STATE}} + hooks: + post: ./.github/scripts/mac-sign-and-notarize.sh "{{.IsSnapshot}}" "gon.hcl" "./dist/syft_{{.Tag}}_{{.Target}}.dmg" + +signs: + - artifacts: checksum + args: ["--output", "${signature}", "--detach-sign", "${artifact}"] nfpms: - license: "Apache 2.0" @@ -50,7 +75,8 @@ brews: archives: - format: tar.gz + builds: + - syft # i.e. Linux only format_overrides: - goos: windows format: zip - diff --git a/Makefile b/Makefile index 8871c8965..c0a6e54e2 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,10 @@ help: ci-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: bootstrap bootstrap: ## Download and install all go dependencies (+ prep tooling in the ./tmp dir) $(call title,Bootstrapping dependencies) @@ -226,8 +230,7 @@ acceptance-test-rpm-package-install: $(SNAPSHOTDIR) 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} \ @@ -241,8 +244,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} \ @@ -258,8 +262,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 @@ -290,4 +298,4 @@ clean-dist: .PHONY: clean-json-schema-examples clean-json-schema-examples: - rm -f schema/json/examples/* \ No newline at end of file + rm -f schema/json/examples/* diff --git a/gon.hcl b/gon.hcl new file mode 100644 index 000000000..57dcd6c11 --- /dev/null +++ b/gon.hcl @@ -0,0 +1,11 @@ +source = ["./dist/syft-macos_darwin_amd64/syft"] # 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.syft" + +sign { + application_identity = "Developer ID Application: ANCHORE, INC. (9MJHKYX5AT)" +} + +dmg { + output_path = "./dist/output.dmg" + volume_name = "Syft" +} diff --git a/test/acceptance/mac.sh b/test/acceptance/mac.sh index 669094855..fade10d76 100755 --- a/test/acceptance/mac.sh +++ b/test/acceptance/mac.sh @@ -37,9 +37,10 @@ skopeo --override-os linux copy docker://docker.io/${TEST_IMAGE} docker-archive: ls -alh ${TEST_IMAGE_TAR} # run syft -chmod 755 ${DISTDIR}/syft_darwin_amd64/syft -${DISTDIR}/syft_darwin_amd64/syft version -SYFT_CHECK_FOR_APP_UPDATE=0 ${DISTDIR}/syft_darwin_amd64/syft docker-archive://${TEST_IMAGE_TAR} -vv -o json > ${REPORT} +SYFT_PATH="${DISTDIR}/syft-macos_darwin_amd64/syft" +chmod 755 "${SYFT_PATH}" +"${SYFT_PATH}" version +SYFT_CHECK_FOR_APP_UPDATE=0 "${SYFT_PATH}" docker-archive://${TEST_IMAGE_TAR} -vv -o json > "${REPORT}" # keep the generated report around mkdir -p ${RESULTSDIR}